1 Commits

Author SHA1 Message Date
Patrick McDonagh
929221c9af First release to App Store 2018-07-02 10:33:17 -05:00
177 changed files with 31294 additions and 4255 deletions

View File

@@ -18,6 +18,7 @@ target 'pocloud' do
pod 'Firebase/Database'
pod 'Firebase/Auth'
pod 'SideMenu'
pod 'Charts'
end

View File

@@ -3,6 +3,9 @@ PODS:
- ChameleonFramework (2.1.0):
- ChameleonFramework/Default (= 2.1.0)
- ChameleonFramework/Default (2.1.0)
- Charts (3.1.1):
- Charts/Core (= 3.1.1)
- Charts/Core (3.1.1)
- Firebase/Auth (5.2.0):
- Firebase/CoreOnly
- FirebaseAuth (= 5.0.1)
@@ -40,15 +43,15 @@ PODS:
- nanopb/encode (= 0.3.8)
- nanopb/decode (0.3.8)
- nanopb/encode (0.3.8)
- PromiseKit/Alamofire (6.2.8):
- PromiseKit/Alamofire (6.3.0):
- Alamofire (~> 4.0)
- PromiseKit/CorePromise
- PromiseKit/CorePromise (6.2.8)
- Realm (3.7.0):
- Realm/Headers (= 3.7.0)
- Realm/Headers (3.7.0)
- RealmSwift (3.7.0):
- Realm (= 3.7.0)
- PromiseKit/CorePromise (6.3.0)
- Realm (3.7.1):
- Realm/Headers (= 3.7.1)
- Realm/Headers (3.7.1)
- RealmSwift (3.7.1):
- Realm (= 3.7.1)
- SideMenu (4.0.0)
- SVProgressHUD (2.2.5)
- SwiftChart (1.0.1)
@@ -57,6 +60,7 @@ PODS:
DEPENDENCIES:
- Alamofire
- ChameleonFramework
- Charts
- Firebase/Auth
- Firebase/Core
- Firebase/Database
@@ -72,6 +76,7 @@ SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Alamofire
- ChameleonFramework
- Charts
- Firebase
- FirebaseAnalytics
- FirebaseAuth
@@ -94,6 +99,7 @@ SPEC REPOS:
SPEC CHECKSUMS:
Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223
ChameleonFramework: d21a3cc247abfe5e37609a283a8238b03575cf64
Charts: 90a4d61da0f6e06684c591e3bcab11940fe61736
Firebase: 25ed0412036d7d008568d1fb4d2e9d81ea8a0a2c
FirebaseAnalytics: b3628aea54c50464c32c393fb2ea032566e7ecc2
FirebaseAuth: 463b8ce33bd5d05f706dcd4615499e3212b4132b
@@ -105,14 +111,14 @@ SPEC CHECKSUMS:
Kingfisher: 976d828df2b24834c6a3f2fc4d82cdbd26552be1
leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
PromiseKit: 6788ce1a0ed5448b83d4aaf56b9fc49fb7647d32
Realm: 4998c6ced1ea15b3792f273b18f8e6faaf935b5c
RealmSwift: 7dc2ab780b9742a1fc7469b2e4776b9773e2e825
PromiseKit: cf84bbb1235a61473b326c5cf0b41f6828f87ba5
Realm: 906be37d52f17f25484ac01643a7f26a9d3bfbd5
RealmSwift: 1c2b6bae3dc55bb87e080ffa96537d71442f6dce
SideMenu: 70ee5657df63ec3382660ec4ef470bf1cf5db07d
SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
SwiftChart: ba767a678d568a5ee22d419e146a0582865e1aff
SwiftyJSON: c29297daf073d2aa016295d5809cdd68045c39b3
PODFILE CHECKSUM: f920b0139378aa95335e4e152b9daa8cfaf65705
PODFILE CHECKSUM: 36ed7324164643785df43e440238496dcacc4f53
COCOAPODS: 1.5.3

202
Pods/Charts/LICENSE generated Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Daniel Cohen Gindi & Philipp Jahoda
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

212
Pods/Charts/README.md generated Normal file
View File

@@ -0,0 +1,212 @@
**Version 3.1.1**, synced to [MPAndroidChart #f6a398b](https://github.com/PhilJay/MPAndroidChart/commit/f6a398b)
![alt tag](https://raw.github.com/danielgindi/Charts/master/Assets/feature_graphic.png)
![Supported Platforms](https://img.shields.io/cocoapods/p/Charts.svg) [![Releases](https://img.shields.io/github/release/danielgindi/Charts.svg)](https://github.com/danielgindi/Charts/releases) [![Latest pod release](https://img.shields.io/cocoapods/v/Charts.svg)](http://cocoapods.org/pods/charts) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Build Status](https://travis-ci.org/danielgindi/Charts.svg?branch=master)](https://travis-ci.org/danielgindi/Charts) [![codecov](https://codecov.io/gh/danielgindi/Charts/branch/master/graph/badge.svg)](https://codecov.io/gh/danielgindi/Charts)
[![Join the chat at https://gitter.im/danielgindi/Charts](https://badges.gitter.im/danielgindi/Charts.svg)](https://gitter.im/danielgindi/Charts?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
### Just a heads up: Charts 3.0 has some breaking changes. Please read [the release/migration notes](https://github.com/danielgindi/Charts/releases/tag/v3.0.0).
### Another heads up: ChartsRealm is now in a [separate repo](https://github.com/danielgindi/ChartsRealm). Pods is also now `Charts` and `ChartsRealm`, instead of ~`Charts/Core`~ and ~`Charts/Realm`~
* Xcode 9.3 / Swift 4.1
* iOS >= 8.0 (Use as an **Embedded** Framework)
* tvOS >= 9.0
* macOS >= 10.11
Okay so there's this beautiful library called [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) by [Philipp Jahoda](https://www.linkedin.com/in/philippjahoda) which has become very popular amongst Android developers, and in the meanwhile there's no decent charting solution for iOS.
I've chosen to write it in `Swift` as it can be highly optimized by the compiler, and can be used in both `Swift` and `ObjC` project. The demo project is written in `ObjC` to demonstrate how it works.
**An amazing feature** of this library now, for Android, iOS, tvOS and macOS, is the time it saves you when developing for both platforms, as the learning curve is singleton- it happens only once, and the code stays very similar so developers don't have to go around and re-invent the app to produce the same output with a different library. (And that's not even considering the fact that there's not really another good choice out there currently...)
## Having trouble running the demo?
* `ChartsDemo/ChartsDemo.xcodeproj` is the demo project for iOS/tvOS
* `ChartsDemo-OSX/ChartsDemo-OSX.xcodeproj` is the demo project for macOS
* Make sure you are running a supported version of Xcode.
* Usually it is specified here a few lines above.
* In most cases it will be the latest Xcode version.
* Make sure that your project supports Swift 3.0
* Optional: Run `carthage checkout` in the project folder, to fetch dependencies (i.e testing dependencies).
* If you don't have Carthage - you can get it [here](https://github.com/Carthage/Carthage/releases).
## Usage
In order to correctly compile:
1. Drag the `Charts.xcodeproj` to your project
2. Go to your target's settings, hit the "+" under the "Embedded Binaries" section, and select the Charts.framework
3. `@import Charts`
4. When using Swift in an ObjC project:
- You need to import your Bridging Header. Usually it is "*YourProject-Swift.h*", so in ChartsDemo it's "*ChartsDemo-Swift.h*". Do not try to actually include "*ChartsDemo-Swift.h*" in your project :-)
- (Xcode 8.1 and earlier) Under "Build Options", mark "Embedded Content Contains Swift Code"
- (Xcode 8.2+) Under "Build Options", mark "Always Embed Swift Standard Libraries"
5. When using [Realm.io](https://realm.io/):
- Note that the Realm framework is not linked with Charts - it is only there for *optional* bindings. Which means that you need to have the framework in your project, and in a compatible version to whatever is compiled with Charts. We will do our best to always compile against the latest version.
- You'll need to add `ChartsRealm` as a dependency too.
## 3rd party tutorials
* [Using Realm and Charts with Swift 3 in iOS 10 (Sami Korpela)](https://medium.com/@skoli/using-realm-and-charts-with-swift-3-in-ios-10-40c42e3838c0#.2gyymwfh8)
* [Creating a Line Chart in Swift 3 and iOS 10 (Osian Smith)](https://medium.com/@OsianSmith/creating-a-line-chart-in-swift-3-and-ios-10-2f647c95392e)
* [Beginning Set-up and Example Using Charts with Swift 3](https://github.com/annalizhaz/ChartsForSwiftBasic)
* Want your tutorial to show here? Create a PR!
## Troubleshooting
#### Can't compile?
* Please note the difference between installing a compiled framework from CocoaPods or Carthage, and copying the source code.
* Please read the **Usage** section again.
* Search in the issues
* Try to politely ask in the issues section
#### Other problems / feature requests
* Search in the issues
* Try to politely ask in the issues section
## CocoaPods Install
Add `pod 'Charts'` to your Podfile. "Charts" is the name of the library.
For [Realm](https://realm.io/) support, please add `pod 'ChartsRealm'` too.
**Note:** ~~`pod 'ios-charts'`~~ is not the correct library, and refers to a different project by someone else.
## Carthage Install
Charts now include Carthage prebuilt binaries.
```carthage
github "danielgindi/Charts" == 3.1.1
github "danielgindi/Charts" ~> 3.1.1
```
In order to build the binaries for a new release, use `carthage build --no-skip-current && carthage archive Charts`.
## 3rd party bindings
Xamarin (by @Flash3001): *iOS* - [GitHub](https://github.com/Flash3001/iOSCharts.Xamarin)/[NuGet](https://www.nuget.org/packages/iOSCharts/). *Android* - [GitHub](https://github.com/Flash3001/MPAndroidChart.Xamarin)/[NuGet](https://www.nuget.org/packages/MPAndroidChart/).
## Help
If you like what you see here, and want to support the work being done in this repository, you could:
* Contribute code, issues and pull requests
* Let people know this library exists (:fire: spread the word :fire:)
* [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=68UL6Y8KUPS96) (You can buy me a beer, or you can buy me dinner :-)
**Note:** The author of [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) is the reason that this library exists, and is accepting [donations](https://github.com/PhilJay/MPAndroidChart#donations) on his page. He deserves them!
Questions & Issues
-----
If you are having questions or problems, you should:
- Make sure you are using the latest version of the library. Check the [**release-section**](https://github.com/danielgindi/Charts/releases).
- Study the Android version's [**Documentation-Wiki**](https://github.com/PhilJay/MPAndroidChart/wiki)
- Study the (Still incomplete [![Doc-Percent](https://img.shields.io/cocoapods/metrics/doc-percent/Charts.svg)](http://cocoadocs.org/docsets/Charts/)) [**Pod-Documentation**](http://cocoadocs.org/docsets/Charts/)
- Search or open questions on [**stackoverflow**](http://stackoverflow.com/questions/tagged/ios-charts) with the `ios-charts` tag
- Search [**known issues**](https://github.com/danielgindi/Charts/issues) for your problem (open and closed)
- Create new issues (please :fire: **search known issues before** :fire:, do not create duplicate issues)
Features
=======
**Core features:**
- 8 different chart types
- Scaling on both axes (with touch-gesture, axes separately or pinch-zoom)
- Dragging / Panning (with touch-gesture)
- Combined-Charts (line-, bar-, scatter-, candle-stick-, bubble-)
- Dual (separate) Axes
- Customizable Axes (both x- and y-axis)
- Highlighting values (with customizable popup-views)
- Save chart to camera-roll / export to PNG/JPEG
- Predefined color templates
- Legends (generated automatically, customizable)
- Animations (build up animations, on both x- and y-axis)
- Limit lines (providing additional information, maximums, ...)
- Fully customizable (paints, typefaces, legends, colors, background, gestures, dashed lines, ...)
- Plotting data directly from [**Realm.io**](https://realm.io) mobile database ([here](https://github.com/danielgindi/ChartsRealm))
**Chart types:**
*Screenshots are currently taken from the original repository, as they render exactly the same :-)*
- **LineChart (with legend, simple design)**
![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/simpledesign_linechart4.png)
- **LineChart (with legend, simple design)**
![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/simpledesign_linechart3.png)
- **LineChart (cubic lines)**
![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/cubiclinechart.png)
- **LineChart (gradient fill)**
![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/line_chart_gradient.png)
- **Combined-Chart (bar- and linechart in this case)**
![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/combined_chart.png)
- **BarChart (with legend, simple design)**
![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/simpledesign_barchart3.png)
- **BarChart (grouped DataSets)**
![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/groupedbarchart.png)
- **Horizontal-BarChart**
![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/horizontal_barchart.png)
- **PieChart (with selection, ...)**
![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/simpledesign_piechart1.png)
- **ScatterChart** (with squares, triangles, circles, ... and more)
![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/scatterchart.png)
- **CandleStickChart** (for financial data)
![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/candlestickchart.png)
- **BubbleChart** (area covered by bubbles indicates the value)
![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/bubblechart.png)
- **RadarChart** (spider web chart)
![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/radarchart.png)
Documentation
=======
Currently there's no need for documentation for the iOS/tvOS/macOS version, as the API is **95% the same** as on Android.
You can read the official [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) documentation here: [**Wiki**](https://github.com/PhilJay/MPAndroidChart/wiki)
Or you can see the [**ChartsDemo**](https://github.com/danielgindi/Charts/tree/master/ChartsDemo) project and learn the how-tos from it.
Special Thanks
=======
Goes to [@liuxuan30](https://github.com/liuxuan30), [@petester42](https://github.com/petester42) and [@AlBirdie](https://github.com/AlBirdie) for new features, bugfixes, and lots and lots of involvement in our open-sourced community! You guys are a huge help to all of those coming here with questions and issues, and I couldn't respond to all of those without you.
License
=======
Copyright 2016 Daniel Cohen Gindi & Philipp Jahoda
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,268 @@
//
// Animator.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc(ChartAnimatorDelegate)
public protocol AnimatorDelegate
{
/// Called when the Animator has stepped.
func animatorUpdated(_ animator: Animator)
/// Called when the Animator has stopped.
func animatorStopped(_ animator: Animator)
}
@objc(ChartAnimator)
open class Animator: NSObject
{
@objc open weak var delegate: AnimatorDelegate?
@objc open var updateBlock: (() -> Void)?
@objc open var stopBlock: (() -> Void)?
/// the phase that is animated and influences the drawn values on the x-axis
@objc open var phaseX: Double = 1.0
/// the phase that is animated and influences the drawn values on the y-axis
@objc open var phaseY: Double = 1.0
private var _startTimeX: TimeInterval = 0.0
private var _startTimeY: TimeInterval = 0.0
private var _displayLink: NSUIDisplayLink?
private var _durationX: TimeInterval = 0.0
private var _durationY: TimeInterval = 0.0
private var _endTimeX: TimeInterval = 0.0
private var _endTimeY: TimeInterval = 0.0
private var _endTime: TimeInterval = 0.0
private var _enabledX: Bool = false
private var _enabledY: Bool = false
private var _easingX: ChartEasingFunctionBlock?
private var _easingY: ChartEasingFunctionBlock?
public override init()
{
super.init()
}
deinit
{
stop()
}
@objc open func stop()
{
guard _displayLink != nil else { return }
_displayLink?.remove(from: .main, forMode: .commonModes)
_displayLink = nil
_enabledX = false
_enabledY = false
// If we stopped an animation in the middle, we do not want to leave it like this
if phaseX != 1.0 || phaseY != 1.0
{
phaseX = 1.0
phaseY = 1.0
delegate?.animatorUpdated(self)
updateBlock?()
}
delegate?.animatorStopped(self)
stopBlock?()
}
private func updateAnimationPhases(_ currentTime: TimeInterval)
{
if _enabledX
{
let elapsedTime: TimeInterval = currentTime - _startTimeX
let duration: TimeInterval = _durationX
var elapsed: TimeInterval = elapsedTime
if elapsed > duration
{
elapsed = duration
}
phaseX = _easingX?(elapsed, duration) ?? elapsed / duration
}
if _enabledY
{
let elapsedTime: TimeInterval = currentTime - _startTimeY
let duration: TimeInterval = _durationY
var elapsed: TimeInterval = elapsedTime
if elapsed > duration
{
elapsed = duration
}
phaseY = _easingY?(elapsed, duration) ?? elapsed / duration
}
}
@objc private func animationLoop()
{
let currentTime: TimeInterval = CACurrentMediaTime()
updateAnimationPhases(currentTime)
delegate?.animatorUpdated(self)
updateBlock?()
if currentTime >= _endTime
{
stop()
}
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easingX: an easing function for the animation on the x axis
/// - parameter easingY: an easing function for the animation on the y axis
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingX: ChartEasingFunctionBlock?, easingY: ChartEasingFunctionBlock?)
{
stop()
_startTimeX = CACurrentMediaTime()
_startTimeY = _startTimeX
_durationX = xAxisDuration
_durationY = yAxisDuration
_endTimeX = _startTimeX + xAxisDuration
_endTimeY = _startTimeY + yAxisDuration
_endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY
_enabledX = xAxisDuration > 0.0
_enabledY = yAxisDuration > 0.0
_easingX = easingX
_easingY = easingY
// Take care of the first frame if rendering is already scheduled...
updateAnimationPhases(_startTimeX)
if _enabledX || _enabledY
{
_displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop))
_displayLink?.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
}
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easingOptionX: the easing function for the animation on the x axis
/// - parameter easingOptionY: the easing function for the animation on the y axis
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOptionX: ChartEasingOption, easingOptionY: ChartEasingOption)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easingFunctionFromOption(easingOptionX), easingY: easingFunctionFromOption(easingOptionY))
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easing: an easing function for the animation
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easing, easingY: easing)
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easingOption: the easing function for the animation
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption))
}
/// Animates the drawing / rendering of the chart the x-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter easing: an easing function for the animation
@objc open func animate(xAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?)
{
_startTimeX = CACurrentMediaTime()
_durationX = xAxisDuration
_endTimeX = _startTimeX + xAxisDuration
_endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY
_enabledX = xAxisDuration > 0.0
_easingX = easing
// Take care of the first frame if rendering is already scheduled...
updateAnimationPhases(_startTimeX)
if _enabledX || _enabledY,
_displayLink == nil
{
_displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop))
_displayLink?.add(to: .main, forMode: .commonModes)
}
}
/// Animates the drawing / rendering of the chart the x-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter easingOption: the easing function for the animation
@objc open func animate(xAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine)
{
animate(xAxisDuration: xAxisDuration, easing: easingFunctionFromOption(easingOption))
}
/// Animates the drawing / rendering of the chart the y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easing: an easing function for the animation
@objc open func animate(yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?)
{
_startTimeY = CACurrentMediaTime()
_durationY = yAxisDuration
_endTimeY = _startTimeY + yAxisDuration
_endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY
_enabledY = yAxisDuration > 0.0
_easingY = easing
// Take care of the first frame if rendering is already scheduled...
updateAnimationPhases(_startTimeY)
if _enabledX || _enabledY,
_displayLink == nil
{
_displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop))
_displayLink?.add(to: .main, forMode: .commonModes)
}
}
/// Animates the drawing / rendering of the chart the y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easingOption: the easing function for the animation
@objc open func animate(yAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine)
{
animate(yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption))
}
}

View File

@@ -0,0 +1,394 @@
//
// ChartAnimationUtils.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public enum ChartEasingOption: Int
{
case linear
case easeInQuad
case easeOutQuad
case easeInOutQuad
case easeInCubic
case easeOutCubic
case easeInOutCubic
case easeInQuart
case easeOutQuart
case easeInOutQuart
case easeInQuint
case easeOutQuint
case easeInOutQuint
case easeInSine
case easeOutSine
case easeInOutSine
case easeInExpo
case easeOutExpo
case easeInOutExpo
case easeInCirc
case easeOutCirc
case easeInOutCirc
case easeInElastic
case easeOutElastic
case easeInOutElastic
case easeInBack
case easeOutBack
case easeInOutBack
case easeInBounce
case easeOutBounce
case easeInOutBounce
}
public typealias ChartEasingFunctionBlock = ((_ elapsed: TimeInterval, _ duration: TimeInterval) -> Double)
internal func easingFunctionFromOption(_ easing: ChartEasingOption) -> ChartEasingFunctionBlock
{
switch easing
{
case .linear:
return EasingFunctions.Linear
case .easeInQuad:
return EasingFunctions.EaseInQuad
case .easeOutQuad:
return EasingFunctions.EaseOutQuad
case .easeInOutQuad:
return EasingFunctions.EaseInOutQuad
case .easeInCubic:
return EasingFunctions.EaseInCubic
case .easeOutCubic:
return EasingFunctions.EaseOutCubic
case .easeInOutCubic:
return EasingFunctions.EaseInOutCubic
case .easeInQuart:
return EasingFunctions.EaseInQuart
case .easeOutQuart:
return EasingFunctions.EaseOutQuart
case .easeInOutQuart:
return EasingFunctions.EaseInOutQuart
case .easeInQuint:
return EasingFunctions.EaseInQuint
case .easeOutQuint:
return EasingFunctions.EaseOutQuint
case .easeInOutQuint:
return EasingFunctions.EaseInOutQuint
case .easeInSine:
return EasingFunctions.EaseInSine
case .easeOutSine:
return EasingFunctions.EaseOutSine
case .easeInOutSine:
return EasingFunctions.EaseInOutSine
case .easeInExpo:
return EasingFunctions.EaseInExpo
case .easeOutExpo:
return EasingFunctions.EaseOutExpo
case .easeInOutExpo:
return EasingFunctions.EaseInOutExpo
case .easeInCirc:
return EasingFunctions.EaseInCirc
case .easeOutCirc:
return EasingFunctions.EaseOutCirc
case .easeInOutCirc:
return EasingFunctions.EaseInOutCirc
case .easeInElastic:
return EasingFunctions.EaseInElastic
case .easeOutElastic:
return EasingFunctions.EaseOutElastic
case .easeInOutElastic:
return EasingFunctions.EaseInOutElastic
case .easeInBack:
return EasingFunctions.EaseInBack
case .easeOutBack:
return EasingFunctions.EaseOutBack
case .easeInOutBack:
return EasingFunctions.EaseInOutBack
case .easeInBounce:
return EasingFunctions.EaseInBounce
case .easeOutBounce:
return EasingFunctions.EaseOutBounce
case .easeInOutBounce:
return EasingFunctions.EaseInOutBounce
}
}
internal struct EasingFunctions
{
internal static let Linear = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in return Double(elapsed / duration) }
internal static let EaseInQuad = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
return position * position
}
internal static let EaseOutQuad = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
return -position * (position - 2.0)
}
internal static let EaseInOutQuad = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / (duration / 2.0))
if position < 1.0
{
return 0.5 * position * position
}
return -0.5 * ((position - 1.0) * (position - 3.0) - 1.0)
}
internal static let EaseInCubic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
return position * position * position
}
internal static let EaseOutCubic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
position -= 1.0
return (position * position * position + 1.0)
}
internal static let EaseInOutCubic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / (duration / 2.0))
if position < 1.0
{
return 0.5 * position * position * position
}
position -= 2.0
return 0.5 * (position * position * position + 2.0)
}
internal static let EaseInQuart = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
return position * position * position * position
}
internal static let EaseOutQuart = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
position -= 1.0
return -(position * position * position * position - 1.0)
}
internal static let EaseInOutQuart = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / (duration / 2.0))
if position < 1.0
{
return 0.5 * position * position * position * position
}
position -= 2.0
return -0.5 * (position * position * position * position - 2.0)
}
internal static let EaseInQuint = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
return position * position * position * position * position
}
internal static let EaseOutQuint = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
position -= 1.0
return (position * position * position * position * position + 1.0)
}
internal static let EaseInOutQuint = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / (duration / 2.0))
if position < 1.0
{
return 0.5 * position * position * position * position * position
}
else
{
position -= 2.0
return 0.5 * (position * position * position * position * position + 2.0)
}
}
internal static let EaseInSine = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position: TimeInterval = elapsed / duration
return Double( -cos(position * Double.pi / 2) + 1.0 )
}
internal static let EaseOutSine = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position: TimeInterval = elapsed / duration
return Double( sin(position * Double.pi / 2) )
}
internal static let EaseInOutSine = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position: TimeInterval = elapsed / duration
return Double( -0.5 * (cos(Double.pi * position) - 1.0) )
}
internal static let EaseInExpo = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
return (elapsed == 0) ? 0.0 : Double(pow(2.0, 10.0 * (elapsed / duration - 1.0)))
}
internal static let EaseOutExpo = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
return (elapsed == duration) ? 1.0 : (-Double(pow(2.0, -10.0 * elapsed / duration)) + 1.0)
}
internal static let EaseInOutExpo = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
if elapsed == 0
{
return 0.0
}
if elapsed == duration
{
return 1.0
}
var position: TimeInterval = elapsed / (duration / 2.0)
if position < 1.0
{
return Double( 0.5 * pow(2.0, 10.0 * (position - 1.0)) )
}
position = position - 1.0
return Double( 0.5 * (-pow(2.0, -10.0 * position) + 2.0) )
}
internal static let EaseInCirc = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
return -(Double(sqrt(1.0 - position * position)) - 1.0)
}
internal static let EaseOutCirc = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position = Double(elapsed / duration)
position -= 1.0
return Double( sqrt(1 - position * position) )
}
internal static let EaseInOutCirc = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position: TimeInterval = elapsed / (duration / 2.0)
if position < 1.0
{
return Double( -0.5 * (sqrt(1.0 - position * position) - 1.0) )
}
position -= 2.0
return Double( 0.5 * (sqrt(1.0 - position * position) + 1.0) )
}
internal static let EaseInElastic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
if elapsed == 0.0
{
return 0.0
}
var position: TimeInterval = elapsed / duration
if position == 1.0
{
return 1.0
}
var p = duration * 0.3
var s = p / (2.0 * Double.pi) * asin(1.0)
position -= 1.0
return Double( -(pow(2.0, 10.0 * position) * sin((position * duration - s) * (2.0 * Double.pi) / p)) )
}
internal static let EaseOutElastic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
if elapsed == 0.0
{
return 0.0
}
var position: TimeInterval = elapsed / duration
if position == 1.0
{
return 1.0
}
var p = duration * 0.3
var s = p / (2.0 * Double.pi) * asin(1.0)
return Double( pow(2.0, -10.0 * position) * sin((position * duration - s) * (2.0 * Double.pi) / p) + 1.0 )
}
internal static let EaseInOutElastic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
if elapsed == 0.0
{
return 0.0
}
var position: TimeInterval = elapsed / (duration / 2.0)
if position == 2.0
{
return 1.0
}
var p = duration * (0.3 * 1.5)
var s = p / (2.0 * Double.pi) * asin(1.0)
if position < 1.0
{
position -= 1.0
return Double( -0.5 * (pow(2.0, 10.0 * position) * sin((position * duration - s) * (2.0 * Double.pi) / p)) )
}
position -= 1.0
return Double( pow(2.0, -10.0 * position) * sin((position * duration - s) * (2.0 * Double.pi) / p) * 0.5 + 1.0 )
}
internal static let EaseInBack = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
let s: TimeInterval = 1.70158
var position: TimeInterval = elapsed / duration
return Double( position * position * ((s + 1.0) * position - s) )
}
internal static let EaseOutBack = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
let s: TimeInterval = 1.70158
var position: TimeInterval = elapsed / duration
position -= 1.0
return Double( position * position * ((s + 1.0) * position + s) + 1.0 )
}
internal static let EaseInOutBack = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var s: TimeInterval = 1.70158
var position: TimeInterval = elapsed / (duration / 2.0)
if position < 1.0
{
s *= 1.525
return Double( 0.5 * (position * position * ((s + 1.0) * position - s)) )
}
s *= 1.525
position -= 2.0
return Double( 0.5 * (position * position * ((s + 1.0) * position + s) + 2.0) )
}
internal static let EaseInBounce = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
return 1.0 - EaseOutBounce(duration - elapsed, duration)
}
internal static let EaseOutBounce = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
var position: TimeInterval = elapsed / duration
if position < (1.0 / 2.75)
{
return Double( 7.5625 * position * position )
}
else if position < (2.0 / 2.75)
{
position -= (1.5 / 2.75)
return Double( 7.5625 * position * position + 0.75 )
}
else if position < (2.5 / 2.75)
{
position -= (2.25 / 2.75)
return Double( 7.5625 * position * position + 0.9375 )
}
else
{
position -= (2.625 / 2.75)
return Double( 7.5625 * position * position + 0.984375 )
}
}
internal static let EaseInOutBounce = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in
if elapsed < (duration / 2.0)
{
return EaseInBounce(elapsed * 2.0, duration) * 0.5
}
return EaseOutBounce(elapsed * 2.0 - duration, duration) * 0.5 + 0.5
}
}

View File

@@ -0,0 +1,183 @@
//
// BarChartView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// Chart that draws bars.
open class BarChartView: BarLineChartViewBase, BarChartDataProvider
{
/// if set to true, all values are drawn above their bars, instead of below their top
private var _drawValueAboveBarEnabled = true
/// if set to true, a grey area is drawn behind each bar that indicates the maximum value
private var _drawBarShadowEnabled = false
internal override func initialize()
{
super.initialize()
renderer = BarChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler)
self.highlighter = BarHighlighter(chart: self)
self.xAxis.spaceMin = 0.5
self.xAxis.spaceMax = 0.5
}
internal override func calcMinMax()
{
guard let data = self.data as? BarChartData
else { return }
if fitBars
{
_xAxis.calculate(
min: data.xMin - data.barWidth / 2.0,
max: data.xMax + data.barWidth / 2.0)
}
else
{
_xAxis.calculate(min: data.xMin, max: data.xMax)
}
// calculate axis range (min / max) according to provided data
leftAxis.calculate(
min: data.getYMin(axis: .left),
max: data.getYMax(axis: .left))
rightAxis.calculate(
min: data.getYMin(axis: .right),
max: data.getYMax(axis: .right))
}
/// - returns: The Highlight object (contains x-index and DataSet index) of the selected value at the given touch point inside the BarChart.
open override func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight?
{
if _data === nil
{
Swift.print("Can't select by touch. No data set.")
return nil
}
guard let h = self.highlighter?.getHighlight(x: pt.x, y: pt.y)
else { return nil }
if !isHighlightFullBarEnabled { return h }
// For isHighlightFullBarEnabled, remove stackIndex
return Highlight(
x: h.x, y: h.y,
xPx: h.xPx, yPx: h.yPx,
dataIndex: h.dataIndex,
dataSetIndex: h.dataSetIndex,
stackIndex: -1,
axis: h.axis)
}
/// - returns: The bounding box of the specified Entry in the specified DataSet. Returns null if the Entry could not be found in the charts data.
@objc open func getBarBounds(entry e: BarChartDataEntry) -> CGRect
{
guard let
data = _data as? BarChartData,
let set = data.getDataSetForEntry(e) as? IBarChartDataSet
else { return CGRect.null }
let y = e.y
let x = e.x
let barWidth = data.barWidth
let left = x - barWidth / 2.0
let right = x + barWidth / 2.0
let top = y >= 0.0 ? y : 0.0
let bottom = y <= 0.0 ? y : 0.0
var bounds = CGRect(x: left, y: top, width: right - left, height: bottom - top)
getTransformer(forAxis: set.axisDependency).rectValueToPixel(&bounds)
return bounds
}
/// Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries.
/// Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified by the parameters.
/// Calls `notifyDataSetChanged()` afterwards.
///
/// - parameter fromX: the starting point on the x-axis where the grouping should begin
/// - parameter groupSpace: the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f
/// - parameter barSpace: the space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f
@objc open func groupBars(fromX: Double, groupSpace: Double, barSpace: Double)
{
guard let barData = self.barData
else
{
Swift.print("You need to set data for the chart before grouping bars.", terminator: "\n")
return
}
barData.groupBars(fromX: fromX, groupSpace: groupSpace, barSpace: barSpace)
notifyDataSetChanged()
}
/// Highlights the value at the given x-value in the given DataSet. Provide -1 as the dataSetIndex to undo all highlighting.
/// - parameter x:
/// - parameter dataSetIndex:
/// - parameter stackIndex: the index inside the stack - only relevant for stacked entries
@objc open func highlightValue(x: Double, dataSetIndex: Int, stackIndex: Int)
{
highlightValue(Highlight(x: x, dataSetIndex: dataSetIndex, stackIndex: stackIndex))
}
// MARK: Accessors
/// if set to true, all values are drawn above their bars, instead of below their top
@objc open var drawValueAboveBarEnabled: Bool
{
get { return _drawValueAboveBarEnabled }
set
{
_drawValueAboveBarEnabled = newValue
setNeedsDisplay()
}
}
/// if set to true, a grey area is drawn behind each bar that indicates the maximum value
@objc open var drawBarShadowEnabled: Bool
{
get { return _drawBarShadowEnabled }
set
{
_drawBarShadowEnabled = newValue
setNeedsDisplay()
}
}
/// Adds half of the bar width to each side of the x-axis range in order to allow the bars of the barchart to be fully displayed.
/// **default**: false
@objc open var fitBars = false
/// Set this to `true` to make the highlight operation full-bar oriented, `false` to make it highlight single values (relevant only for stacked).
/// If enabled, highlighting operations will highlight the whole bar, even if only a single stack entry was tapped.
@objc open var highlightFullBarEnabled: Bool = false
/// - returns: `true` the highlight is be full-bar oriented, `false` ifsingle-value
open var isHighlightFullBarEnabled: Bool { return highlightFullBarEnabled }
// MARK: - BarChartDataProvider
open var barData: BarChartData? { return _data as? BarChartData }
/// - returns: `true` if drawing values above bars is enabled, `false` ifnot
open var isDrawValueAboveBarEnabled: Bool { return drawValueAboveBarEnabled }
/// - returns: `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot
open var isDrawBarShadowEnabled: Bool { return drawBarShadowEnabled }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
//
// BubbleChartView.swift
// Charts
//
// Bubble chart implementation:
// Copyright 2015 Pierre-Marc Airoldi
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class BubbleChartView: BarLineChartViewBase, BubbleChartDataProvider
{
open override func initialize()
{
super.initialize()
renderer = BubbleChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler)
}
// MARK: - BubbleChartDataProvider
open var bubbleData: BubbleChartData? { return _data as? BubbleChartData }
}

View File

@@ -0,0 +1,34 @@
//
// CandleStickChartView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// Financial chart type that draws candle-sticks.
open class CandleStickChartView: BarLineChartViewBase, CandleChartDataProvider
{
internal override func initialize()
{
super.initialize()
renderer = CandleStickChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler)
self.xAxis.spaceMin = 0.5
self.xAxis.spaceMax = 0.5
}
// MARK: - CandleChartDataProvider
open var candleData: CandleChartData?
{
return _data as? CandleChartData
}
}

View File

@@ -0,0 +1,992 @@
//
// ChartViewBase.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
// Based on https://github.com/PhilJay/MPAndroidChart/commit/c42b880
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc
public protocol ChartViewDelegate
{
/// Called when a value has been selected inside the chart.
/// - parameter entry: The selected Entry.
/// - parameter highlight: The corresponding highlight object that contains information about the highlighted position such as dataSetIndex etc.
@objc optional func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight)
// Called when nothing has been selected or an "un-select" has been made.
@objc optional func chartValueNothingSelected(_ chartView: ChartViewBase)
// Callbacks when the chart is scaled / zoomed via pinch zoom gesture.
@objc optional func chartScaled(_ chartView: ChartViewBase, scaleX: CGFloat, scaleY: CGFloat)
// Callbacks when the chart is moved / translated via drag gesture.
@objc optional func chartTranslated(_ chartView: ChartViewBase, dX: CGFloat, dY: CGFloat)
}
open class ChartViewBase: NSUIView, ChartDataProvider, AnimatorDelegate
{
// MARK: - Properties
/// - returns: The object representing all x-labels, this method can be used to
/// acquire the XAxis object and modify it (e.g. change the position of the
/// labels)
@objc open var xAxis: XAxis
{
return _xAxis
}
/// The default IValueFormatter that has been determined by the chart considering the provided minimum and maximum values.
internal var _defaultValueFormatter: IValueFormatter? = DefaultValueFormatter(decimals: 0)
/// object that holds all data that was originally set for the chart, before it was modified or any filtering algorithms had been applied
internal var _data: ChartData?
/// Flag that indicates if highlighting per tap (touch) is enabled
private var _highlightPerTapEnabled = true
/// If set to true, chart continues to scroll after touch up
@objc open var dragDecelerationEnabled = true
/// Deceleration friction coefficient in [0 ; 1] interval, higher values indicate that speed will decrease slowly, for example if it set to 0, it will stop immediately.
/// 1 is an invalid value, and will be converted to 0.999 automatically.
private var _dragDecelerationFrictionCoef: CGFloat = 0.9
/// if true, units are drawn next to the values in the chart
internal var _drawUnitInChart = false
/// The object representing the labels on the x-axis
internal var _xAxis: XAxis!
/// The `Description` object of the chart.
/// This should have been called just "description", but
@objc open var chartDescription: Description?
/// The legend object containing all data associated with the legend
internal var _legend: Legend!
/// delegate to receive chart events
@objc open weak var delegate: ChartViewDelegate?
/// text that is displayed when the chart is empty
@objc open var noDataText = "No chart data available."
/// Font to be used for the no data text.
@objc open var noDataFont: NSUIFont! = NSUIFont(name: "HelveticaNeue", size: 12.0)
/// color of the no data text
@objc open var noDataTextColor: NSUIColor = NSUIColor.black
/// alignment of the no data text
open var noDataTextAlignment: NSTextAlignment = .left
internal var _legendRenderer: LegendRenderer!
/// object responsible for rendering the data
@objc open var renderer: DataRenderer?
@objc open var highlighter: IHighlighter?
/// object that manages the bounds and drawing constraints of the chart
internal var _viewPortHandler: ViewPortHandler!
/// object responsible for animations
internal var _animator: Animator!
/// flag that indicates if offsets calculation has already been done or not
private var _offsetsCalculated = false
/// array of Highlight objects that reference the highlighted slices in the chart
internal var _indicesToHighlight = [Highlight]()
/// `true` if drawing the marker is enabled when tapping on values
/// (use the `marker` property to specify a marker)
@objc open var drawMarkers = true
/// - returns: `true` if drawing the marker is enabled when tapping on values
/// (use the `marker` property to specify a marker)
@objc open var isDrawMarkersEnabled: Bool { return drawMarkers }
/// The marker that is displayed when a value is clicked on the chart
@objc open var marker: IMarker?
private var _interceptTouchEvents = false
/// An extra offset to be appended to the viewport's top
@objc open var extraTopOffset: CGFloat = 0.0
/// An extra offset to be appended to the viewport's right
@objc open var extraRightOffset: CGFloat = 0.0
/// An extra offset to be appended to the viewport's bottom
@objc open var extraBottomOffset: CGFloat = 0.0
/// An extra offset to be appended to the viewport's left
@objc open var extraLeftOffset: CGFloat = 0.0
@objc open func setExtraOffsets(left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat)
{
extraLeftOffset = left
extraTopOffset = top
extraRightOffset = right
extraBottomOffset = bottom
}
// MARK: - Initializers
public override init(frame: CGRect)
{
super.init(frame: frame)
initialize()
}
public required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
initialize()
}
deinit
{
self.removeObserver(self, forKeyPath: "bounds")
self.removeObserver(self, forKeyPath: "frame")
}
internal func initialize()
{
#if os(iOS)
self.backgroundColor = NSUIColor.clear
#endif
_animator = Animator()
_animator.delegate = self
_viewPortHandler = ViewPortHandler(width: bounds.size.width, height: bounds.size.height)
chartDescription = Description()
_legend = Legend()
_legendRenderer = LegendRenderer(viewPortHandler: _viewPortHandler, legend: _legend)
_xAxis = XAxis()
self.addObserver(self, forKeyPath: "bounds", options: .new, context: nil)
self.addObserver(self, forKeyPath: "frame", options: .new, context: nil)
}
// MARK: - ChartViewBase
/// The data for the chart
open var data: ChartData?
{
get
{
return _data
}
set
{
_data = newValue
_offsetsCalculated = false
guard let _data = _data else
{
setNeedsDisplay()
return
}
// calculate how many digits are needed
setupDefaultFormatter(min: _data.getYMin(), max: _data.getYMax())
for set in _data.dataSets
{
if set.needsFormatter || set.valueFormatter === _defaultValueFormatter
{
set.valueFormatter = _defaultValueFormatter
}
}
// let the chart know there is new data
notifyDataSetChanged()
}
}
/// Clears the chart from all data (sets it to null) and refreshes it (by calling setNeedsDisplay()).
@objc open func clear()
{
_data = nil
_offsetsCalculated = false
_indicesToHighlight.removeAll()
lastHighlighted = nil
setNeedsDisplay()
}
/// Removes all DataSets (and thereby Entries) from the chart. Does not set the data object to nil. Also refreshes the chart by calling setNeedsDisplay().
@objc open func clearValues()
{
_data?.clearValues()
setNeedsDisplay()
}
/// - returns: `true` if the chart is empty (meaning it's data object is either null or contains no entries).
@objc open func isEmpty() -> Bool
{
guard let data = _data else { return true }
if data.entryCount <= 0
{
return true
}
else
{
return false
}
}
/// Lets the chart know its underlying data has changed and should perform all necessary recalculations.
/// It is crucial that this method is called everytime data is changed dynamically. Not calling this method can lead to crashes or unexpected behaviour.
@objc open func notifyDataSetChanged()
{
fatalError("notifyDataSetChanged() cannot be called on ChartViewBase")
}
/// Calculates the offsets of the chart to the border depending on the position of an eventual legend or depending on the length of the y-axis and x-axis labels and their position
internal func calculateOffsets()
{
fatalError("calculateOffsets() cannot be called on ChartViewBase")
}
/// calcualtes the y-min and y-max value and the y-delta and x-delta value
internal func calcMinMax()
{
fatalError("calcMinMax() cannot be called on ChartViewBase")
}
/// calculates the required number of digits for the values that might be drawn in the chart (if enabled), and creates the default value formatter
internal func setupDefaultFormatter(min: Double, max: Double)
{
// check if a custom formatter is set or not
var reference = Double(0.0)
if let data = _data , data.entryCount >= 2
{
reference = fabs(max - min)
}
else
{
let absMin = fabs(min)
let absMax = fabs(max)
reference = absMin > absMax ? absMin : absMax
}
if _defaultValueFormatter is DefaultValueFormatter
{
// setup the formatter with a new number of digits
let digits = reference.decimalPlaces
(_defaultValueFormatter as? DefaultValueFormatter)?.decimals
= digits
}
}
open override func draw(_ rect: CGRect)
{
let optionalContext = NSUIGraphicsGetCurrentContext()
guard let context = optionalContext else { return }
let frame = self.bounds
if _data === nil && noDataText.count > 0
{
context.saveGState()
defer { context.restoreGState() }
let paragraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.minimumLineHeight = noDataFont.lineHeight
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.alignment = noDataTextAlignment
ChartUtils.drawMultilineText(
context: context,
text: noDataText,
point: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0),
attributes:
[.font: noDataFont,
.foregroundColor: noDataTextColor,
.paragraphStyle: paragraphStyle],
constrainedToSize: self.bounds.size,
anchor: CGPoint(x: 0.5, y: 0.5),
angleRadians: 0.0)
return
}
if !_offsetsCalculated
{
calculateOffsets()
_offsetsCalculated = true
}
}
/// Draws the description text in the bottom right corner of the chart (per default)
internal func drawDescription(context: CGContext)
{
// check if description should be drawn
guard
let description = chartDescription,
description.isEnabled,
let descriptionText = description.text,
descriptionText.count > 0
else { return }
let position = description.position ?? CGPoint(x: bounds.width - _viewPortHandler.offsetRight - description.xOffset,
y: bounds.height - _viewPortHandler.offsetBottom - description.yOffset - description.font.lineHeight)
var attrs = [NSAttributedStringKey : Any]()
attrs[NSAttributedStringKey.font] = description.font
attrs[NSAttributedStringKey.foregroundColor] = description.textColor
ChartUtils.drawText(
context: context,
text: descriptionText,
point: position,
align: description.textAlign,
attributes: attrs)
}
// MARK: - Highlighting
/// - returns: The array of currently highlighted values. This might an empty if nothing is highlighted.
@objc open var highlighted: [Highlight]
{
return _indicesToHighlight
}
/// Set this to false to prevent values from being highlighted by tap gesture.
/// Values can still be highlighted via drag or programmatically.
/// **default**: true
@objc open var highlightPerTapEnabled: Bool
{
get { return _highlightPerTapEnabled }
set { _highlightPerTapEnabled = newValue }
}
/// - returns: `true` if values can be highlighted via tap gesture, `false` ifnot.
@objc open var isHighLightPerTapEnabled: Bool
{
return highlightPerTapEnabled
}
/// Checks if the highlight array is null, has a length of zero or if the first object is null.
/// - returns: `true` if there are values to highlight, `false` ifthere are no values to highlight.
@objc open func valuesToHighlight() -> Bool
{
return _indicesToHighlight.count > 0
}
/// Highlights the values at the given indices in the given DataSets. Provide
/// null or an empty array to undo all highlighting.
/// This should be used to programmatically highlight values.
/// This method *will not* call the delegate.
@objc open func highlightValues(_ highs: [Highlight]?)
{
// set the indices to highlight
_indicesToHighlight = highs ?? [Highlight]()
if _indicesToHighlight.isEmpty
{
self.lastHighlighted = nil
}
else
{
self.lastHighlighted = _indicesToHighlight[0]
}
// redraw the chart
setNeedsDisplay()
}
/// Highlights any y-value at the given x-value in the given DataSet.
/// Provide -1 as the dataSetIndex to undo all highlighting.
/// This method will call the delegate.
/// - parameter x: The x-value to highlight
/// - parameter dataSetIndex: The dataset index to search in
/// - parameter dataIndex: The data index to search in (only used in CombinedChartView currently)
@objc open func highlightValue(x: Double, dataSetIndex: Int, dataIndex: Int = -1)
{
highlightValue(x: x, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: true)
}
/// Highlights the value at the given x-value and y-value in the given DataSet.
/// Provide -1 as the dataSetIndex to undo all highlighting.
/// This method will call the delegate.
/// - parameter x: The x-value to highlight
/// - parameter y: The y-value to highlight. Supply `NaN` for "any"
/// - parameter dataSetIndex: The dataset index to search in
/// - parameter dataIndex: The data index to search in (only used in CombinedChartView currently)
@objc open func highlightValue(x: Double, y: Double, dataSetIndex: Int, dataIndex: Int = -1)
{
highlightValue(x: x, y: y, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: true)
}
/// Highlights any y-value at the given x-value in the given DataSet.
/// Provide -1 as the dataSetIndex to undo all highlighting.
/// - parameter x: The x-value to highlight
/// - parameter dataSetIndex: The dataset index to search in
/// - parameter dataIndex: The data index to search in (only used in CombinedChartView currently)
/// - parameter callDelegate: Should the delegate be called for this change
@objc open func highlightValue(x: Double, dataSetIndex: Int, dataIndex: Int = -1, callDelegate: Bool)
{
highlightValue(x: x, y: .nan, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: callDelegate)
}
/// Highlights the value at the given x-value and y-value in the given DataSet.
/// Provide -1 as the dataSetIndex to undo all highlighting.
/// - parameter x: The x-value to highlight
/// - parameter y: The y-value to highlight. Supply `NaN` for "any"
/// - parameter dataSetIndex: The dataset index to search in
/// - parameter dataIndex: The data index to search in (only used in CombinedChartView currently)
/// - parameter callDelegate: Should the delegate be called for this change
@objc open func highlightValue(x: Double, y: Double, dataSetIndex: Int, dataIndex: Int = -1, callDelegate: Bool)
{
guard let data = _data else
{
Swift.print("Value not highlighted because data is nil")
return
}
if dataSetIndex < 0 || dataSetIndex >= data.dataSetCount
{
highlightValue(nil, callDelegate: callDelegate)
}
else
{
highlightValue(Highlight(x: x, y: y, dataSetIndex: dataSetIndex, dataIndex: dataIndex), callDelegate: callDelegate)
}
}
/// Highlights the values represented by the provided Highlight object
/// This method *will not* call the delegate.
/// - parameter highlight: contains information about which entry should be highlighted
@objc open func highlightValue(_ highlight: Highlight?)
{
highlightValue(highlight, callDelegate: false)
}
/// Highlights the value selected by touch gesture.
@objc open func highlightValue(_ highlight: Highlight?, callDelegate: Bool)
{
var entry: ChartDataEntry?
var h = highlight
if h == nil
{
self.lastHighlighted = nil
_indicesToHighlight.removeAll(keepingCapacity: false)
}
else
{
// set the indices to highlight
entry = _data?.entryForHighlight(h!)
if entry == nil
{
h = nil
_indicesToHighlight.removeAll(keepingCapacity: false)
}
else
{
_indicesToHighlight = [h!]
}
}
if callDelegate, let delegate = delegate
{
if let h = h
{
// notify the listener
delegate.chartValueSelected?(self, entry: entry!, highlight: h)
}
else
{
delegate.chartValueNothingSelected?(self)
}
}
// redraw the chart
setNeedsDisplay()
}
/// - returns: The Highlight object (contains x-index and DataSet index) of the
/// selected value at the given touch point inside the Line-, Scatter-, or
/// CandleStick-Chart.
@objc open func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight?
{
if _data === nil
{
Swift.print("Can't select by touch. No data set.")
return nil
}
return self.highlighter?.getHighlight(x: pt.x, y: pt.y)
}
/// The last value that was highlighted via touch.
@objc open var lastHighlighted: Highlight?
// MARK: - Markers
/// draws all MarkerViews on the highlighted positions
internal func drawMarkers(context: CGContext)
{
// if there is no marker view or drawing marker is disabled
guard
let marker = marker
, isDrawMarkersEnabled &&
valuesToHighlight()
else { return }
for i in 0 ..< _indicesToHighlight.count
{
let highlight = _indicesToHighlight[i]
guard let
set = data?.getDataSetByIndex(highlight.dataSetIndex),
let e = _data?.entryForHighlight(highlight)
else { continue }
let entryIndex = set.entryIndex(entry: e)
if entryIndex > Int(Double(set.entryCount) * _animator.phaseX)
{
continue
}
let pos = getMarkerPosition(highlight: highlight)
// check bounds
if !_viewPortHandler.isInBounds(x: pos.x, y: pos.y)
{
continue
}
// callbacks to update the content
marker.refreshContent(entry: e, highlight: highlight)
// draw the marker
marker.draw(context: context, point: pos)
}
}
/// - returns: The actual position in pixels of the MarkerView for the given Entry in the given DataSet.
@objc open func getMarkerPosition(highlight: Highlight) -> CGPoint
{
return CGPoint(x: highlight.drawX, y: highlight.drawY)
}
// MARK: - Animation
/// - returns: The animator responsible for animating chart values.
@objc open var chartAnimator: Animator!
{
return _animator
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easingX: an easing function for the animation on the x axis
/// - parameter easingY: an easing function for the animation on the y axis
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingX: ChartEasingFunctionBlock?, easingY: ChartEasingFunctionBlock?)
{
_animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easingX, easingY: easingY)
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easingOptionX: the easing function for the animation on the x axis
/// - parameter easingOptionY: the easing function for the animation on the y axis
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOptionX: ChartEasingOption, easingOptionY: ChartEasingOption)
{
_animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingOptionX: easingOptionX, easingOptionY: easingOptionY)
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easing: an easing function for the animation
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?)
{
_animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easing: easing)
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easingOption: the easing function for the animation
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOption: ChartEasingOption)
{
_animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingOption: easingOption)
}
/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter yAxisDuration: duration for animating the y axis
@objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval)
{
_animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration)
}
/// Animates the drawing / rendering of the chart the x-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter easing: an easing function for the animation
@objc open func animate(xAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?)
{
_animator.animate(xAxisDuration: xAxisDuration, easing: easing)
}
/// Animates the drawing / rendering of the chart the x-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
/// - parameter easingOption: the easing function for the animation
@objc open func animate(xAxisDuration: TimeInterval, easingOption: ChartEasingOption)
{
_animator.animate(xAxisDuration: xAxisDuration, easingOption: easingOption)
}
/// Animates the drawing / rendering of the chart the x-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter xAxisDuration: duration for animating the x axis
@objc open func animate(xAxisDuration: TimeInterval)
{
_animator.animate(xAxisDuration: xAxisDuration)
}
/// Animates the drawing / rendering of the chart the y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easing: an easing function for the animation
@objc open func animate(yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?)
{
_animator.animate(yAxisDuration: yAxisDuration, easing: easing)
}
/// Animates the drawing / rendering of the chart the y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter yAxisDuration: duration for animating the y axis
/// - parameter easingOption: the easing function for the animation
@objc open func animate(yAxisDuration: TimeInterval, easingOption: ChartEasingOption)
{
_animator.animate(yAxisDuration: yAxisDuration, easingOption: easingOption)
}
/// Animates the drawing / rendering of the chart the y-axis with the specified animation time.
/// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart.
/// - parameter yAxisDuration: duration for animating the y axis
@objc open func animate(yAxisDuration: TimeInterval)
{
_animator.animate(yAxisDuration: yAxisDuration)
}
// MARK: - Accessors
/// - returns: The current y-max value across all DataSets
open var chartYMax: Double
{
return _data?.yMax ?? 0.0
}
/// - returns: The current y-min value across all DataSets
open var chartYMin: Double
{
return _data?.yMin ?? 0.0
}
open var chartXMax: Double
{
return _xAxis._axisMaximum
}
open var chartXMin: Double
{
return _xAxis._axisMinimum
}
open var xRange: Double
{
return _xAxis.axisRange
}
/// *
/// - note: (Equivalent of getCenter() in MPAndroidChart, as center is already a standard in iOS that returns the center point relative to superview, and MPAndroidChart returns relative to self)*
/// - returns: The center point of the chart (the whole View) in pixels.
@objc open var midPoint: CGPoint
{
let bounds = self.bounds
return CGPoint(x: bounds.origin.x + bounds.size.width / 2.0, y: bounds.origin.y + bounds.size.height / 2.0)
}
/// - returns: The center of the chart taking offsets under consideration. (returns the center of the content rectangle)
open var centerOffsets: CGPoint
{
return _viewPortHandler.contentCenter
}
/// - returns: The Legend object of the chart. This method can be used to get an instance of the legend in order to customize the automatically generated Legend.
@objc open var legend: Legend
{
return _legend
}
/// - returns: The renderer object responsible for rendering / drawing the Legend.
@objc open var legendRenderer: LegendRenderer!
{
return _legendRenderer
}
/// - returns: The rectangle that defines the borders of the chart-value surface (into which the actual values are drawn).
@objc open var contentRect: CGRect
{
return _viewPortHandler.contentRect
}
/// - returns: The ViewPortHandler of the chart that is responsible for the
/// content area of the chart and its offsets and dimensions.
@objc open var viewPortHandler: ViewPortHandler!
{
return _viewPortHandler
}
/// - returns: The bitmap that represents the chart.
@objc open func getChartImage(transparent: Bool) -> NSUIImage?
{
NSUIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque || !transparent, NSUIMainScreen()?.nsuiScale ?? 1.0)
guard let context = NSUIGraphicsGetCurrentContext()
else { return nil }
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
if isOpaque || !transparent
{
// Background color may be partially transparent, we must fill with white if we want to output an opaque image
context.setFillColor(NSUIColor.white.cgColor)
context.fill(rect)
if let backgroundColor = self.backgroundColor
{
context.setFillColor(backgroundColor.cgColor)
context.fill(rect)
}
}
nsuiLayer?.render(in: context)
let image = NSUIGraphicsGetImageFromCurrentImageContext()
NSUIGraphicsEndImageContext()
return image
}
public enum ImageFormat
{
case jpeg
case png
}
/// Saves the current chart state with the given name to the given path on
/// the sdcard leaving the path empty "" will put the saved file directly on
/// the SD card chart is saved as a PNG image, example:
/// saveToPath("myfilename", "foldername1/foldername2")
///
/// - parameter to: path to the image to save
/// - parameter format: the format to save
/// - parameter compressionQuality: compression quality for lossless formats (JPEG)
///
/// - returns: `true` if the image was saved successfully
open func save(to path: String, format: ImageFormat, compressionQuality: Double) -> Bool
{
guard let image = getChartImage(transparent: format != .jpeg) else { return false }
let imageData: Data?
switch (format)
{
case .png: imageData = NSUIImagePNGRepresentation(image)
case .jpeg: imageData = NSUIImageJPEGRepresentation(image, CGFloat(compressionQuality))
}
guard let data = imageData else { return false }
do
{
try data.write(to: URL(fileURLWithPath: path), options: .atomic)
}
catch
{
return false
}
return true
}
internal var _viewportJobs = [ViewPortJob]()
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
if keyPath == "bounds" || keyPath == "frame"
{
let bounds = self.bounds
if (_viewPortHandler !== nil &&
(bounds.size.width != _viewPortHandler.chartWidth ||
bounds.size.height != _viewPortHandler.chartHeight))
{
_viewPortHandler.setChartDimens(width: bounds.size.width, height: bounds.size.height)
// This may cause the chart view to mutate properties affecting the view port -- lets do this
// before we try to run any pending jobs on the view port itself
notifyDataSetChanged()
// Finish any pending viewport changes
while (!_viewportJobs.isEmpty)
{
let job = _viewportJobs.remove(at: 0)
job.doJob()
}
}
}
}
@objc open func removeViewportJob(_ job: ViewPortJob)
{
if let index = _viewportJobs.index(where: { $0 === job })
{
_viewportJobs.remove(at: index)
}
}
@objc open func clearAllViewportJobs()
{
_viewportJobs.removeAll(keepingCapacity: false)
}
@objc open func addViewportJob(_ job: ViewPortJob)
{
if _viewPortHandler.hasChartDimens
{
job.doJob()
}
else
{
_viewportJobs.append(job)
}
}
/// **default**: true
/// - returns: `true` if chart continues to scroll after touch up, `false` ifnot.
@objc open var isDragDecelerationEnabled: Bool
{
return dragDecelerationEnabled
}
/// Deceleration friction coefficient in [0 ; 1] interval, higher values indicate that speed will decrease slowly, for example if it set to 0, it will stop immediately.
/// 1 is an invalid value, and will be converted to 0.999 automatically.
///
/// **default**: true
@objc open var dragDecelerationFrictionCoef: CGFloat
{
get
{
return _dragDecelerationFrictionCoef
}
set
{
var val = newValue
if val < 0.0
{
val = 0.0
}
if val >= 1.0
{
val = 0.999
}
_dragDecelerationFrictionCoef = val
}
}
/// The maximum distance in screen pixels away from an entry causing it to highlight.
/// **default**: 500.0
open var maxHighlightDistance: CGFloat = 500.0
/// the number of maximum visible drawn values on the chart only active when `drawValuesEnabled` is enabled
open var maxVisibleCount: Int
{
return Int(INT_MAX)
}
// MARK: - AnimatorDelegate
open func animatorUpdated(_ chartAnimator: Animator)
{
setNeedsDisplay()
}
open func animatorStopped(_ chartAnimator: Animator)
{
}
// MARK: - Touches
open override func nsuiTouchesBegan(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
{
if !_interceptTouchEvents
{
super.nsuiTouchesBegan(touches, withEvent: event)
}
}
open override func nsuiTouchesMoved(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
{
if !_interceptTouchEvents
{
super.nsuiTouchesMoved(touches, withEvent: event)
}
}
open override func nsuiTouchesEnded(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
{
if !_interceptTouchEvents
{
super.nsuiTouchesEnded(touches, withEvent: event)
}
}
open override func nsuiTouchesCancelled(_ touches: Set<NSUITouch>?, withEvent event: NSUIEvent?)
{
if !_interceptTouchEvents
{
super.nsuiTouchesCancelled(touches, withEvent: event)
}
}
}

View File

@@ -0,0 +1,246 @@
//
// CombinedChartView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// This chart class allows the combination of lines, bars, scatter and candle data all displayed in one chart area.
open class CombinedChartView: BarLineChartViewBase, CombinedChartDataProvider
{
/// the fill-formatter used for determining the position of the fill-line
internal var _fillFormatter: IFillFormatter!
/// enum that allows to specify the order in which the different data objects for the combined-chart are drawn
@objc(CombinedChartDrawOrder)
public enum DrawOrder: Int
{
case bar
case bubble
case line
case candle
case scatter
}
open override func initialize()
{
super.initialize()
self.highlighter = CombinedHighlighter(chart: self, barDataProvider: self)
// Old default behaviour
self.highlightFullBarEnabled = true
_fillFormatter = DefaultFillFormatter()
renderer = CombinedChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler)
}
open override var data: ChartData?
{
get
{
return super.data
}
set
{
super.data = newValue
self.highlighter = CombinedHighlighter(chart: self, barDataProvider: self)
(renderer as? CombinedChartRenderer)?.createRenderers()
renderer?.initBuffers()
}
}
@objc open var fillFormatter: IFillFormatter
{
get
{
return _fillFormatter
}
set
{
_fillFormatter = newValue
if _fillFormatter == nil
{
_fillFormatter = DefaultFillFormatter()
}
}
}
/// - returns: The Highlight object (contains x-index and DataSet index) of the selected value at the given touch point inside the CombinedChart.
open override func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight?
{
if _data === nil
{
Swift.print("Can't select by touch. No data set.")
return nil
}
guard let h = self.highlighter?.getHighlight(x: pt.x, y: pt.y)
else { return nil }
if !isHighlightFullBarEnabled { return h }
// For isHighlightFullBarEnabled, remove stackIndex
return Highlight(
x: h.x, y: h.y,
xPx: h.xPx, yPx: h.yPx,
dataIndex: h.dataIndex,
dataSetIndex: h.dataSetIndex,
stackIndex: -1,
axis: h.axis)
}
// MARK: - CombinedChartDataProvider
open var combinedData: CombinedChartData?
{
get
{
return _data as? CombinedChartData
}
}
// MARK: - LineChartDataProvider
open var lineData: LineChartData?
{
get
{
return combinedData?.lineData
}
}
// MARK: - BarChartDataProvider
open var barData: BarChartData?
{
get
{
return combinedData?.barData
}
}
// MARK: - ScatterChartDataProvider
open var scatterData: ScatterChartData?
{
get
{
return combinedData?.scatterData
}
}
// MARK: - CandleChartDataProvider
open var candleData: CandleChartData?
{
get
{
return combinedData?.candleData
}
}
// MARK: - BubbleChartDataProvider
open var bubbleData: BubbleChartData?
{
get
{
return combinedData?.bubbleData
}
}
// MARK: - Accessors
/// if set to true, all values are drawn above their bars, instead of below their top
@objc open var drawValueAboveBarEnabled: Bool
{
get { return (renderer as! CombinedChartRenderer).drawValueAboveBarEnabled }
set { (renderer as! CombinedChartRenderer).drawValueAboveBarEnabled = newValue }
}
/// if set to true, a grey area is drawn behind each bar that indicates the maximum value
@objc open var drawBarShadowEnabled: Bool
{
get { return (renderer as! CombinedChartRenderer).drawBarShadowEnabled }
set { (renderer as! CombinedChartRenderer).drawBarShadowEnabled = newValue }
}
/// - returns: `true` if drawing values above bars is enabled, `false` ifnot
open var isDrawValueAboveBarEnabled: Bool { return (renderer as! CombinedChartRenderer).drawValueAboveBarEnabled }
/// - returns: `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot
open var isDrawBarShadowEnabled: Bool { return (renderer as! CombinedChartRenderer).drawBarShadowEnabled }
/// the order in which the provided data objects should be drawn.
/// The earlier you place them in the provided array, the further they will be in the background.
/// e.g. if you provide [DrawOrder.Bar, DrawOrder.Line], the bars will be drawn behind the lines.
@objc open var drawOrder: [Int]
{
get
{
return (renderer as! CombinedChartRenderer).drawOrder.map { $0.rawValue }
}
set
{
(renderer as! CombinedChartRenderer).drawOrder = newValue.map { DrawOrder(rawValue: $0)! }
}
}
/// Set this to `true` to make the highlight operation full-bar oriented, `false` to make it highlight single values
@objc open var highlightFullBarEnabled: Bool = false
/// - returns: `true` the highlight is be full-bar oriented, `false` ifsingle-value
open var isHighlightFullBarEnabled: Bool { return highlightFullBarEnabled }
// MARK: - ChartViewBase
/// draws all MarkerViews on the highlighted positions
override func drawMarkers(context: CGContext)
{
guard
let marker = marker,
isDrawMarkersEnabled && valuesToHighlight()
else { return }
for i in 0 ..< _indicesToHighlight.count
{
let highlight = _indicesToHighlight[i]
guard
let set = combinedData?.getDataSetByHighlight(highlight),
let e = _data?.entryForHighlight(highlight)
else { continue }
let entryIndex = set.entryIndex(entry: e)
if entryIndex > Int(Double(set.entryCount) * _animator.phaseX)
{
continue
}
let pos = getMarkerPosition(highlight: highlight)
// check bounds
if !_viewPortHandler.isInBounds(x: pos.x, y: pos.y)
{
continue
}
// callbacks to update the content
marker.refreshContent(entry: e, highlight: highlight)
// draw the marker
marker.draw(context: context, point: pos)
}
}
}

View File

@@ -0,0 +1,274 @@
//
// HorizontalBarChartView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
/// BarChart with horizontal bar orientation. In this implementation, x- and y-axis are switched.
open class HorizontalBarChartView: BarChartView
{
internal override func initialize()
{
super.initialize()
_leftAxisTransformer = TransformerHorizontalBarChart(viewPortHandler: _viewPortHandler)
_rightAxisTransformer = TransformerHorizontalBarChart(viewPortHandler: _viewPortHandler)
renderer = HorizontalBarChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler)
leftYAxisRenderer = YAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: leftAxis, transformer: _leftAxisTransformer)
rightYAxisRenderer = YAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: rightAxis, transformer: _rightAxisTransformer)
xAxisRenderer = XAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer, chart: self)
self.highlighter = HorizontalBarHighlighter(chart: self)
}
internal override func calculateLegendOffsets(offsetLeft: inout CGFloat, offsetTop: inout CGFloat, offsetRight: inout CGFloat, offsetBottom: inout CGFloat)
{
guard
let legend = _legend,
legend.isEnabled,
legend.drawInside
else { return }
// setup offsets for legend
switch legend.orientation
{
case .vertical:
switch legend.horizontalAlignment
{
case .left:
offsetLeft += min(legend.neededWidth, _viewPortHandler.chartWidth * legend.maxSizePercent) + legend.xOffset
case .right:
offsetRight += min(legend.neededWidth, _viewPortHandler.chartWidth * legend.maxSizePercent) + legend.xOffset
case .center:
switch legend.verticalAlignment
{
case .top:
offsetTop += min(legend.neededHeight, _viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset
case .bottom:
offsetBottom += min(legend.neededHeight, _viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset
default:
break
}
}
case .horizontal:
switch legend.verticalAlignment
{
case .top:
offsetTop += min(legend.neededHeight, _viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset
// left axis equals the top x-axis in a horizontal chart
if leftAxis.isEnabled && leftAxis.isDrawLabelsEnabled
{
offsetTop += leftAxis.getRequiredHeightSpace()
}
case .bottom:
offsetBottom += min(legend.neededHeight, _viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset
// right axis equals the bottom x-axis in a horizontal chart
if rightAxis.isEnabled && rightAxis.isDrawLabelsEnabled
{
offsetBottom += rightAxis.getRequiredHeightSpace()
}
default:
break
}
}
}
internal override func calculateOffsets()
{
var offsetLeft: CGFloat = 0.0,
offsetRight: CGFloat = 0.0,
offsetTop: CGFloat = 0.0,
offsetBottom: CGFloat = 0.0
calculateLegendOffsets(offsetLeft: &offsetLeft,
offsetTop: &offsetTop,
offsetRight: &offsetRight,
offsetBottom: &offsetBottom)
// offsets for y-labels
if leftAxis.needsOffset
{
offsetTop += leftAxis.getRequiredHeightSpace()
}
if rightAxis.needsOffset
{
offsetBottom += rightAxis.getRequiredHeightSpace()
}
let xlabelwidth = _xAxis.labelRotatedWidth
if _xAxis.isEnabled
{
// offsets for x-labels
if _xAxis.labelPosition == .bottom
{
offsetLeft += xlabelwidth
}
else if _xAxis.labelPosition == .top
{
offsetRight += xlabelwidth
}
else if _xAxis.labelPosition == .bothSided
{
offsetLeft += xlabelwidth
offsetRight += xlabelwidth
}
}
offsetTop += self.extraTopOffset
offsetRight += self.extraRightOffset
offsetBottom += self.extraBottomOffset
offsetLeft += self.extraLeftOffset
_viewPortHandler.restrainViewPort(
offsetLeft: max(self.minOffset, offsetLeft),
offsetTop: max(self.minOffset, offsetTop),
offsetRight: max(self.minOffset, offsetRight),
offsetBottom: max(self.minOffset, offsetBottom))
prepareOffsetMatrix()
prepareValuePxMatrix()
}
internal override func prepareValuePxMatrix()
{
_rightAxisTransformer.prepareMatrixValuePx(chartXMin: rightAxis._axisMinimum, deltaX: CGFloat(rightAxis.axisRange), deltaY: CGFloat(_xAxis.axisRange), chartYMin: _xAxis._axisMinimum)
_leftAxisTransformer.prepareMatrixValuePx(chartXMin: leftAxis._axisMinimum, deltaX: CGFloat(leftAxis.axisRange), deltaY: CGFloat(_xAxis.axisRange), chartYMin: _xAxis._axisMinimum)
}
open override func getMarkerPosition(highlight: Highlight) -> CGPoint
{
return CGPoint(x: highlight.drawY, y: highlight.drawX)
}
open override func getBarBounds(entry e: BarChartDataEntry) -> CGRect
{
guard
let data = _data as? BarChartData,
let set = data.getDataSetForEntry(e) as? IBarChartDataSet
else { return CGRect.null }
let y = e.y
let x = e.x
let barWidth = data.barWidth
let top = x - 0.5 + barWidth / 2.0
let bottom = x + 0.5 - barWidth / 2.0
let left = y >= 0.0 ? y : 0.0
let right = y <= 0.0 ? y : 0.0
var bounds = CGRect(x: left, y: top, width: right - left, height: bottom - top)
getTransformer(forAxis: set.axisDependency).rectValueToPixel(&bounds)
return bounds
}
open override func getPosition(entry e: ChartDataEntry, axis: YAxis.AxisDependency) -> CGPoint
{
var vals = CGPoint(x: CGFloat(e.y), y: CGFloat(e.x))
getTransformer(forAxis: axis).pointValueToPixel(&vals)
return vals
}
open override func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight?
{
if _data === nil
{
Swift.print("Can't select by touch. No data set.", terminator: "\n")
return nil
}
return self.highlighter?.getHighlight(x: pt.y, y: pt.x)
}
/// - returns: The lowest x-index (value on the x-axis) that is still visible on he chart.
open override var lowestVisibleX: Double
{
var pt = CGPoint(
x: viewPortHandler.contentLeft,
y: viewPortHandler.contentBottom)
getTransformer(forAxis: .left).pixelToValues(&pt)
return max(xAxis._axisMinimum, Double(pt.y))
}
/// - returns: The highest x-index (value on the x-axis) that is still visible on the chart.
open override var highestVisibleX: Double
{
var pt = CGPoint(
x: viewPortHandler.contentLeft,
y: viewPortHandler.contentTop)
getTransformer(forAxis: .left).pixelToValues(&pt)
return min(xAxis._axisMaximum, Double(pt.y))
}
// MARK: - Viewport
open override func setVisibleXRangeMaximum(_ maxXRange: Double)
{
let xScale = xAxis.axisRange / maxXRange
viewPortHandler.setMinimumScaleY(CGFloat(xScale))
}
open override func setVisibleXRangeMinimum(_ minXRange: Double)
{
let xScale = xAxis.axisRange / minXRange
viewPortHandler.setMaximumScaleY(CGFloat(xScale))
}
open override func setVisibleXRange(minXRange: Double, maxXRange: Double)
{
let minScale = xAxis.axisRange / minXRange
let maxScale = xAxis.axisRange / maxXRange
viewPortHandler.setMinMaxScaleY(minScaleY: CGFloat(minScale), maxScaleY: CGFloat(maxScale))
}
open override func setVisibleYRangeMaximum(_ maxYRange: Double, axis: YAxis.AxisDependency)
{
let yScale = getAxisRange(axis: axis) / maxYRange
viewPortHandler.setMinimumScaleX(CGFloat(yScale))
}
open override func setVisibleYRangeMinimum(_ minYRange: Double, axis: YAxis.AxisDependency)
{
let yScale = getAxisRange(axis: axis) / minYRange
viewPortHandler.setMaximumScaleX(CGFloat(yScale))
}
open override func setVisibleYRange(minYRange: Double, maxYRange: Double, axis: YAxis.AxisDependency)
{
let minScale = getAxisRange(axis: axis) / minYRange
let maxScale = getAxisRange(axis: axis) / maxYRange
viewPortHandler.setMinMaxScaleX(minScaleX: CGFloat(minScale), maxScaleX: CGFloat(maxScale))
}
}

View File

@@ -0,0 +1,28 @@
//
// LineChartView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// Chart that draws lines, surfaces, circles, ...
open class LineChartView: BarLineChartViewBase, LineChartDataProvider
{
internal override func initialize()
{
super.initialize()
renderer = LineChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler)
}
// MARK: - LineChartDataProvider
open var lineData: LineChartData? { return _data as? LineChartData }
}

View File

@@ -0,0 +1,642 @@
//
// PieChartView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
/// View that represents a pie chart. Draws cake like slices.
open class PieChartView: PieRadarChartViewBase
{
/// rect object that represents the bounds of the piechart, needed for drawing the circle
private var _circleBox = CGRect()
/// flag indicating if entry labels should be drawn or not
private var _drawEntryLabelsEnabled = true
/// array that holds the width of each pie-slice in degrees
private var _drawAngles = [CGFloat]()
/// array that holds the absolute angle in degrees of each slice
private var _absoluteAngles = [CGFloat]()
/// if true, the hole inside the chart will be drawn
private var _drawHoleEnabled = true
private var _holeColor: NSUIColor? = NSUIColor.white
/// Sets the color the entry labels are drawn with.
private var _entryLabelColor: NSUIColor? = NSUIColor.white
/// Sets the font the entry labels are drawn with.
private var _entryLabelFont: NSUIFont? = NSUIFont(name: "HelveticaNeue", size: 13.0)
/// if true, the hole will see-through to the inner tips of the slices
private var _drawSlicesUnderHoleEnabled = false
/// if true, the values inside the piechart are drawn as percent values
private var _usePercentValuesEnabled = false
/// variable for the text that is drawn in the center of the pie-chart
private var _centerAttributedText: NSAttributedString?
/// the offset on the x- and y-axis the center text has in dp.
private var _centerTextOffset: CGPoint = CGPoint()
/// indicates the size of the hole in the center of the piechart
///
/// **default**: `0.5`
private var _holeRadiusPercent = CGFloat(0.5)
private var _transparentCircleColor: NSUIColor? = NSUIColor(white: 1.0, alpha: 105.0/255.0)
/// the radius of the transparent circle next to the chart-hole in the center
private var _transparentCircleRadiusPercent = CGFloat(0.55)
/// if enabled, centertext is drawn
private var _drawCenterTextEnabled = true
private var _centerTextRadiusPercent: CGFloat = 1.0
/// maximum angle for this pie
private var _maxAngle: CGFloat = 360.0
public override init(frame: CGRect)
{
super.init(frame: frame)
}
public required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
internal override func initialize()
{
super.initialize()
renderer = PieChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler)
_xAxis = nil
self.highlighter = PieHighlighter(chart: self)
}
open override func draw(_ rect: CGRect)
{
super.draw(rect)
if _data === nil
{
return
}
let optionalContext = NSUIGraphicsGetCurrentContext()
guard let context = optionalContext, let renderer = renderer else
{
return
}
renderer.drawData(context: context)
if (valuesToHighlight())
{
renderer.drawHighlighted(context: context, indices: _indicesToHighlight)
}
renderer.drawExtras(context: context)
renderer.drawValues(context: context)
legendRenderer.renderLegend(context: context)
drawDescription(context: context)
drawMarkers(context: context)
}
internal override func calculateOffsets()
{
super.calculateOffsets()
// prevent nullpointer when no data set
if _data === nil
{
return
}
let radius = diameter / 2.0
let c = self.centerOffsets
let shift = (data as? PieChartData)?.dataSet?.selectionShift ?? 0.0
// create the circle box that will contain the pie-chart (the bounds of the pie-chart)
_circleBox.origin.x = (c.x - radius) + shift
_circleBox.origin.y = (c.y - radius) + shift
_circleBox.size.width = diameter - shift * 2.0
_circleBox.size.height = diameter - shift * 2.0
}
internal override func calcMinMax()
{
calcAngles()
}
open override func getMarkerPosition(highlight: Highlight) -> CGPoint
{
let center = self.centerCircleBox
var r = self.radius
var off = r / 10.0 * 3.6
if self.isDrawHoleEnabled
{
off = (r - (r * self.holeRadiusPercent)) / 2.0
}
r -= off // offset to keep things inside the chart
let rotationAngle = self.rotationAngle
let entryIndex = Int(highlight.x)
// offset needed to center the drawn text in the slice
let offset = drawAngles[entryIndex] / 2.0
// calculate the text position
let x: CGFloat = (r * cos(((rotationAngle + absoluteAngles[entryIndex] - offset) * CGFloat(_animator.phaseY)).DEG2RAD) + center.x)
let y: CGFloat = (r * sin(((rotationAngle + absoluteAngles[entryIndex] - offset) * CGFloat(_animator.phaseY)).DEG2RAD) + center.y)
return CGPoint(x: x, y: y)
}
/// calculates the needed angles for the chart slices
private func calcAngles()
{
_drawAngles = [CGFloat]()
_absoluteAngles = [CGFloat]()
guard let data = _data else { return }
let entryCount = data.entryCount
_drawAngles.reserveCapacity(entryCount)
_absoluteAngles.reserveCapacity(entryCount)
let yValueSum = (_data as! PieChartData).yValueSum
var dataSets = data.dataSets
var cnt = 0
for i in 0 ..< data.dataSetCount
{
let set = dataSets[i]
let entryCount = set.entryCount
for j in 0 ..< entryCount
{
guard let e = set.entryForIndex(j) else { continue }
_drawAngles.append(calcAngle(value: abs(e.y), yValueSum: yValueSum))
if cnt == 0
{
_absoluteAngles.append(_drawAngles[cnt])
}
else
{
_absoluteAngles.append(_absoluteAngles[cnt - 1] + _drawAngles[cnt])
}
cnt += 1
}
}
}
/// Checks if the given index is set to be highlighted.
@objc open func needsHighlight(index: Int) -> Bool
{
// no highlight
if !valuesToHighlight()
{
return false
}
for i in 0 ..< _indicesToHighlight.count
{
// check if the xvalue for the given dataset needs highlight
if Int(_indicesToHighlight[i].x) == index
{
return true
}
}
return false
}
/// calculates the needed angle for a given value
private func calcAngle(_ value: Double) -> CGFloat
{
return calcAngle(value: value, yValueSum: (_data as! PieChartData).yValueSum)
}
/// calculates the needed angle for a given value
private func calcAngle(value: Double, yValueSum: Double) -> CGFloat
{
return CGFloat(value) / CGFloat(yValueSum) * _maxAngle
}
/// This will throw an exception, PieChart has no XAxis object.
open override var xAxis: XAxis
{
fatalError("PieChart has no XAxis")
}
open override func indexForAngle(_ angle: CGFloat) -> Int
{
// take the current angle of the chart into consideration
let a = (angle - self.rotationAngle).normalizedAngle
for i in 0 ..< _absoluteAngles.count
{
if _absoluteAngles[i] > a
{
return i
}
}
return -1 // return -1 if no index found
}
/// - returns: The index of the DataSet this x-index belongs to.
@objc open func dataSetIndexForIndex(_ xValue: Double) -> Int
{
var dataSets = _data?.dataSets ?? []
for i in 0 ..< dataSets.count
{
if (dataSets[i].entryForXValue(xValue, closestToY: Double.nan) !== nil)
{
return i
}
}
return -1
}
/// - returns: An integer array of all the different angles the chart slices
/// have the angles in the returned array determine how much space (of 360°)
/// each slice takes
@objc open var drawAngles: [CGFloat]
{
return _drawAngles
}
/// - returns: The absolute angles of the different chart slices (where the
/// slices end)
@objc open var absoluteAngles: [CGFloat]
{
return _absoluteAngles
}
/// The color for the hole that is drawn in the center of the PieChart (if enabled).
///
/// - note: Use holeTransparent with holeColor = nil to make the hole transparent.*
@objc open var holeColor: NSUIColor?
{
get
{
return _holeColor
}
set
{
_holeColor = newValue
setNeedsDisplay()
}
}
/// if true, the hole will see-through to the inner tips of the slices
///
/// **default**: `false`
@objc open var drawSlicesUnderHoleEnabled: Bool
{
get
{
return _drawSlicesUnderHoleEnabled
}
set
{
_drawSlicesUnderHoleEnabled = newValue
setNeedsDisplay()
}
}
/// - returns: `true` if the inner tips of the slices are visible behind the hole, `false` if not.
@objc open var isDrawSlicesUnderHoleEnabled: Bool
{
return drawSlicesUnderHoleEnabled
}
/// `true` if the hole in the center of the pie-chart is set to be visible, `false` ifnot
@objc open var drawHoleEnabled: Bool
{
get
{
return _drawHoleEnabled
}
set
{
_drawHoleEnabled = newValue
setNeedsDisplay()
}
}
/// - returns: `true` if the hole in the center of the pie-chart is set to be visible, `false` ifnot
@objc open var isDrawHoleEnabled: Bool
{
get
{
return drawHoleEnabled
}
}
/// the text that is displayed in the center of the pie-chart
@objc open var centerText: String?
{
get
{
return self.centerAttributedText?.string
}
set
{
var attrString: NSMutableAttributedString?
if newValue == nil
{
attrString = nil
}
else
{
#if os(OSX)
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.lineBreakMode = NSParagraphStyle.LineBreakMode.byTruncatingTail
#else
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.lineBreakMode = NSLineBreakMode.byTruncatingTail
#endif
paragraphStyle.alignment = .center
attrString = NSMutableAttributedString(string: newValue!)
attrString?.setAttributes([
NSAttributedStringKey.foregroundColor: NSUIColor.black,
NSAttributedStringKey.font: NSUIFont.systemFont(ofSize: 12.0),
NSAttributedStringKey.paragraphStyle: paragraphStyle
], range: NSMakeRange(0, attrString!.length))
}
self.centerAttributedText = attrString
}
}
/// the text that is displayed in the center of the pie-chart
@objc open var centerAttributedText: NSAttributedString?
{
get
{
return _centerAttributedText
}
set
{
_centerAttributedText = newValue
setNeedsDisplay()
}
}
/// Sets the offset the center text should have from it's original position in dp. Default x = 0, y = 0
@objc open var centerTextOffset: CGPoint
{
get
{
return _centerTextOffset
}
set
{
_centerTextOffset = newValue
setNeedsDisplay()
}
}
/// `true` if drawing the center text is enabled
@objc open var drawCenterTextEnabled: Bool
{
get
{
return _drawCenterTextEnabled
}
set
{
_drawCenterTextEnabled = newValue
setNeedsDisplay()
}
}
/// - returns: `true` if drawing the center text is enabled
@objc open var isDrawCenterTextEnabled: Bool
{
get
{
return drawCenterTextEnabled
}
}
internal override var requiredLegendOffset: CGFloat
{
return _legend.font.pointSize * 2.0
}
internal override var requiredBaseOffset: CGFloat
{
return 0.0
}
open override var radius: CGFloat
{
return _circleBox.width / 2.0
}
/// - returns: The circlebox, the boundingbox of the pie-chart slices
@objc open var circleBox: CGRect
{
return _circleBox
}
/// - returns: The center of the circlebox
@objc open var centerCircleBox: CGPoint
{
return CGPoint(x: _circleBox.midX, y: _circleBox.midY)
}
/// the radius of the hole in the center of the piechart in percent of the maximum radius (max = the radius of the whole chart)
///
/// **default**: 0.5 (50%) (half the pie)
@objc open var holeRadiusPercent: CGFloat
{
get
{
return _holeRadiusPercent
}
set
{
_holeRadiusPercent = newValue
setNeedsDisplay()
}
}
/// The color that the transparent-circle should have.
///
/// **default**: `nil`
@objc open var transparentCircleColor: NSUIColor?
{
get
{
return _transparentCircleColor
}
set
{
_transparentCircleColor = newValue
setNeedsDisplay()
}
}
/// the radius of the transparent circle that is drawn next to the hole in the piechart in percent of the maximum radius (max = the radius of the whole chart)
///
/// **default**: 0.55 (55%) -> means 5% larger than the center-hole by default
@objc open var transparentCircleRadiusPercent: CGFloat
{
get
{
return _transparentCircleRadiusPercent
}
set
{
_transparentCircleRadiusPercent = newValue
setNeedsDisplay()
}
}
/// The color the entry labels are drawn with.
@objc open var entryLabelColor: NSUIColor?
{
get { return _entryLabelColor }
set
{
_entryLabelColor = newValue
setNeedsDisplay()
}
}
/// The font the entry labels are drawn with.
@objc open var entryLabelFont: NSUIFont?
{
get { return _entryLabelFont }
set
{
_entryLabelFont = newValue
setNeedsDisplay()
}
}
/// Set this to true to draw the enrty labels into the pie slices
@objc open var drawEntryLabelsEnabled: Bool
{
get
{
return _drawEntryLabelsEnabled
}
set
{
_drawEntryLabelsEnabled = newValue
setNeedsDisplay()
}
}
/// - returns: `true` if drawing entry labels is enabled, `false` ifnot
@objc open var isDrawEntryLabelsEnabled: Bool
{
get
{
return drawEntryLabelsEnabled
}
}
/// If this is enabled, values inside the PieChart are drawn in percent and not with their original value. Values provided for the ValueFormatter to format are then provided in percent.
@objc open var usePercentValuesEnabled: Bool
{
get
{
return _usePercentValuesEnabled
}
set
{
_usePercentValuesEnabled = newValue
setNeedsDisplay()
}
}
/// - returns: `true` if drawing x-values is enabled, `false` ifnot
@objc open var isUsePercentValuesEnabled: Bool
{
get
{
return usePercentValuesEnabled
}
}
/// the rectangular radius of the bounding box for the center text, as a percentage of the pie hole
@objc open var centerTextRadiusPercent: CGFloat
{
get
{
return _centerTextRadiusPercent
}
set
{
_centerTextRadiusPercent = newValue
setNeedsDisplay()
}
}
/// The max angle that is used for calculating the pie-circle.
/// 360 means it's a full pie-chart, 180 results in a half-pie-chart.
/// **default**: 360.0
@objc open var maxAngle: CGFloat
{
get
{
return _maxAngle
}
set
{
_maxAngle = newValue
if _maxAngle > 360.0
{
_maxAngle = 360.0
}
if _maxAngle < 90.0
{
_maxAngle = 90.0
}
}
}
}

View File

@@ -0,0 +1,866 @@
//
// PieRadarChartViewBase.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
/// Base class of PieChartView and RadarChartView.
open class PieRadarChartViewBase: ChartViewBase
{
/// holds the normalized version of the current rotation angle of the chart
private var _rotationAngle = CGFloat(270.0)
/// holds the raw version of the current rotation angle of the chart
private var _rawRotationAngle = CGFloat(270.0)
/// flag that indicates if rotation is enabled or not
@objc open var rotationEnabled = true
/// Sets the minimum offset (padding) around the chart, defaults to 0.0
@objc open var minOffset = CGFloat(0.0)
/// iOS && OSX only: Enabled multi-touch rotation using two fingers.
private var _rotationWithTwoFingers = false
private var _tapGestureRecognizer: NSUITapGestureRecognizer!
#if !os(tvOS)
private var _rotationGestureRecognizer: NSUIRotationGestureRecognizer!
#endif
public override init(frame: CGRect)
{
super.init(frame: frame)
}
public required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
deinit
{
stopDeceleration()
}
internal override func initialize()
{
super.initialize()
_tapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:)))
self.addGestureRecognizer(_tapGestureRecognizer)
#if !os(tvOS)
_rotationGestureRecognizer = NSUIRotationGestureRecognizer(target: self, action: #selector(rotationGestureRecognized(_:)))
self.addGestureRecognizer(_rotationGestureRecognizer)
_rotationGestureRecognizer.isEnabled = rotationWithTwoFingers
#endif
}
internal override func calcMinMax()
{
/*_xAxis.axisRange = Double((_data?.xVals.count ?? 0) - 1)*/
}
open override var maxVisibleCount: Int
{
get
{
return data?.entryCount ?? 0
}
}
open override func notifyDataSetChanged()
{
calcMinMax()
if let data = _data , _legend !== nil
{
legendRenderer.computeLegend(data: data)
}
calculateOffsets()
setNeedsDisplay()
}
internal override func calculateOffsets()
{
var legendLeft = CGFloat(0.0)
var legendRight = CGFloat(0.0)
var legendBottom = CGFloat(0.0)
var legendTop = CGFloat(0.0)
if _legend != nil && _legend.enabled && !_legend.drawInside
{
let fullLegendWidth = min(_legend.neededWidth, _viewPortHandler.chartWidth * _legend.maxSizePercent)
switch _legend.orientation
{
case .vertical:
var xLegendOffset: CGFloat = 0.0
if _legend.horizontalAlignment == .left
|| _legend.horizontalAlignment == .right
{
if _legend.verticalAlignment == .center
{
// this is the space between the legend and the chart
let spacing = CGFloat(13.0)
xLegendOffset = fullLegendWidth + spacing
}
else
{
// this is the space between the legend and the chart
let spacing = CGFloat(8.0)
let legendWidth = fullLegendWidth + spacing
let legendHeight = _legend.neededHeight + _legend.textHeightMax
let c = self.midPoint
let bottomX = _legend.horizontalAlignment == .right
? self.bounds.width - legendWidth + 15.0
: legendWidth - 15.0
let bottomY = legendHeight + 15
let distLegend = distanceToCenter(x: bottomX, y: bottomY)
let reference = getPosition(center: c, dist: self.radius,
angle: angleForPoint(x: bottomX, y: bottomY))
let distReference = distanceToCenter(x: reference.x, y: reference.y)
let minOffset = CGFloat(5.0)
if bottomY >= c.y
&& self.bounds.height - legendWidth > self.bounds.width
{
xLegendOffset = legendWidth
}
else if distLegend < distReference
{
let diff = distReference - distLegend
xLegendOffset = minOffset + diff
}
}
}
switch _legend.horizontalAlignment
{
case .left:
legendLeft = xLegendOffset
case .right:
legendRight = xLegendOffset
case .center:
switch _legend.verticalAlignment
{
case .top:
legendTop = min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent)
case .bottom:
legendBottom = min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent)
default:
break
}
}
case .horizontal:
var yLegendOffset: CGFloat = 0.0
if _legend.verticalAlignment == .top
|| _legend.verticalAlignment == .bottom
{
// It's possible that we do not need this offset anymore as it
// is available through the extraOffsets, but changing it can mean
// changing default visibility for existing apps.
let yOffset = self.requiredLegendOffset
yLegendOffset = min(
_legend.neededHeight + yOffset,
_viewPortHandler.chartHeight * _legend.maxSizePercent)
}
switch _legend.verticalAlignment
{
case .top:
legendTop = yLegendOffset
case .bottom:
legendBottom = yLegendOffset
default:
break
}
}
legendLeft += self.requiredBaseOffset
legendRight += self.requiredBaseOffset
legendTop += self.requiredBaseOffset
legendBottom += self.requiredBaseOffset
}
legendTop += self.extraTopOffset
legendRight += self.extraRightOffset
legendBottom += self.extraBottomOffset
legendLeft += self.extraLeftOffset
var minOffset = self.minOffset
if self is RadarChartView
{
let x = self.xAxis
if x.isEnabled && x.drawLabelsEnabled
{
minOffset = max(minOffset, x.labelRotatedWidth)
}
}
let offsetLeft = max(minOffset, legendLeft)
let offsetTop = max(minOffset, legendTop)
let offsetRight = max(minOffset, legendRight)
let offsetBottom = max(minOffset, max(self.requiredBaseOffset, legendBottom))
_viewPortHandler.restrainViewPort(offsetLeft: offsetLeft, offsetTop: offsetTop, offsetRight: offsetRight, offsetBottom: offsetBottom)
}
/// - returns: The angle relative to the chart center for the given point on the chart in degrees.
/// The angle is always between 0 and 360°, 0° is NORTH, 90° is EAST, ...
@objc open func angleForPoint(x: CGFloat, y: CGFloat) -> CGFloat
{
let c = centerOffsets
let tx = Double(x - c.x)
let ty = Double(y - c.y)
let length = sqrt(tx * tx + ty * ty)
let r = acos(ty / length)
var angle = r.RAD2DEG
if x > c.x
{
angle = 360.0 - angle
}
// add 90° because chart starts EAST
angle = angle + 90.0
// neutralize overflow
if angle > 360.0
{
angle = angle - 360.0
}
return CGFloat(angle)
}
/// Calculates the position around a center point, depending on the distance
/// from the center, and the angle of the position around the center.
@objc open func getPosition(center: CGPoint, dist: CGFloat, angle: CGFloat) -> CGPoint
{
return CGPoint(x: center.x + dist * cos(angle.DEG2RAD),
y: center.y + dist * sin(angle.DEG2RAD))
}
/// - returns: The distance of a certain point on the chart to the center of the chart.
@objc open func distanceToCenter(x: CGFloat, y: CGFloat) -> CGFloat
{
let c = self.centerOffsets
var dist = CGFloat(0.0)
var xDist = CGFloat(0.0)
var yDist = CGFloat(0.0)
if x > c.x
{
xDist = x - c.x
}
else
{
xDist = c.x - x
}
if y > c.y
{
yDist = y - c.y
}
else
{
yDist = c.y - y
}
// pythagoras
dist = sqrt(pow(xDist, 2.0) + pow(yDist, 2.0))
return dist
}
/// - returns: The xIndex for the given angle around the center of the chart.
/// -1 if not found / outofbounds.
@objc open func indexForAngle(_ angle: CGFloat) -> Int
{
fatalError("indexForAngle() cannot be called on PieRadarChartViewBase")
}
/// current rotation angle of the pie chart
///
/// **default**: 270 --> top (NORTH)
/// - returns: Will always return a normalized value, which will be between 0.0 < 360.0
@objc open var rotationAngle: CGFloat
{
get
{
return _rotationAngle
}
set
{
_rawRotationAngle = newValue
_rotationAngle = newValue.normalizedAngle
setNeedsDisplay()
}
}
/// gets the raw version of the current rotation angle of the pie chart the returned value could be any value, negative or positive, outside of the 360 degrees.
/// this is used when working with rotation direction, mainly by gestures and animations.
@objc open var rawRotationAngle: CGFloat
{
return _rawRotationAngle
}
/// - returns: The diameter of the pie- or radar-chart
@objc open var diameter: CGFloat
{
var content = _viewPortHandler.contentRect
content.origin.x += extraLeftOffset
content.origin.y += extraTopOffset
content.size.width -= extraLeftOffset + extraRightOffset
content.size.height -= extraTopOffset + extraBottomOffset
return min(content.width, content.height)
}
/// - returns: The radius of the chart in pixels.
@objc open var radius: CGFloat
{
fatalError("radius cannot be called on PieRadarChartViewBase")
}
/// - returns: The required offset for the chart legend.
internal var requiredLegendOffset: CGFloat
{
fatalError("requiredLegendOffset cannot be called on PieRadarChartViewBase")
}
/// - returns: The base offset needed for the chart without calculating the
/// legend size.
internal var requiredBaseOffset: CGFloat
{
fatalError("requiredBaseOffset cannot be called on PieRadarChartViewBase")
}
open override var chartYMax: Double
{
return 0.0
}
open override var chartYMin: Double
{
return 0.0
}
@objc open var isRotationEnabled: Bool { return rotationEnabled }
/// flag that indicates if rotation is done with two fingers or one.
/// when the chart is inside a scrollview, you need a two-finger rotation because a one-finger rotation eats up all touch events.
///
/// On iOS this will disable one-finger rotation.
/// On OSX this will keep two-finger multitouch rotation, and one-pointer mouse rotation.
///
/// **default**: false
@objc open var rotationWithTwoFingers: Bool
{
get
{
return _rotationWithTwoFingers
}
set
{
_rotationWithTwoFingers = newValue
#if !os(tvOS)
_rotationGestureRecognizer.isEnabled = _rotationWithTwoFingers
#endif
}
}
/// flag that indicates if rotation is done with two fingers or one.
/// when the chart is inside a scrollview, you need a two-finger rotation because a one-finger rotation eats up all touch events.
///
/// On iOS this will disable one-finger rotation.
/// On OSX this will keep two-finger multitouch rotation, and one-pointer mouse rotation.
///
/// **default**: false
@objc open var isRotationWithTwoFingers: Bool
{
return _rotationWithTwoFingers
}
// MARK: - Animation
private var _spinAnimator: Animator!
/// Applys a spin animation to the Chart.
@objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat, easing: ChartEasingFunctionBlock?)
{
if _spinAnimator != nil
{
_spinAnimator.stop()
}
_spinAnimator = Animator()
_spinAnimator.updateBlock = {
self.rotationAngle = (toAngle - fromAngle) * CGFloat(self._spinAnimator.phaseX) + fromAngle
}
_spinAnimator.stopBlock = { self._spinAnimator = nil }
_spinAnimator.animate(xAxisDuration: duration, easing: easing)
}
@objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat, easingOption: ChartEasingOption)
{
spin(duration: duration, fromAngle: fromAngle, toAngle: toAngle, easing: easingFunctionFromOption(easingOption))
}
@objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat)
{
spin(duration: duration, fromAngle: fromAngle, toAngle: toAngle, easing: nil)
}
@objc open func stopSpinAnimation()
{
if _spinAnimator != nil
{
_spinAnimator.stop()
}
}
// MARK: - Gestures
private var _rotationGestureStartPoint: CGPoint!
private var _isRotating = false
private var _startAngle = CGFloat(0.0)
private struct AngularVelocitySample
{
var time: TimeInterval
var angle: CGFloat
}
private var _velocitySamples = [AngularVelocitySample]()
private var _decelerationLastTime: TimeInterval = 0.0
private var _decelerationDisplayLink: NSUIDisplayLink!
private var _decelerationAngularVelocity: CGFloat = 0.0
internal final func processRotationGestureBegan(location: CGPoint)
{
self.resetVelocity()
if rotationEnabled
{
self.sampleVelocity(touchLocation: location)
}
self.setGestureStartAngle(x: location.x, y: location.y)
_rotationGestureStartPoint = location
}
internal final func processRotationGestureMoved(location: CGPoint)
{
if isDragDecelerationEnabled
{
sampleVelocity(touchLocation: location)
}
if !_isRotating &&
distance(
eventX: location.x,
startX: _rotationGestureStartPoint.x,
eventY: location.y,
startY: _rotationGestureStartPoint.y) > CGFloat(8.0)
{
_isRotating = true
}
else
{
self.updateGestureRotation(x: location.x, y: location.y)
setNeedsDisplay()
}
}
internal final func processRotationGestureEnded(location: CGPoint)
{
if isDragDecelerationEnabled
{
stopDeceleration()
sampleVelocity(touchLocation: location)
_decelerationAngularVelocity = calculateVelocity()
if _decelerationAngularVelocity != 0.0
{
_decelerationLastTime = CACurrentMediaTime()
_decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(PieRadarChartViewBase.decelerationLoop))
_decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
}
}
}
internal final func processRotationGestureCancelled()
{
if _isRotating
{
_isRotating = false
}
}
#if !os(OSX)
open override func nsuiTouchesBegan(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
{
// if rotation by touch is enabled
if rotationEnabled
{
stopDeceleration()
if !rotationWithTwoFingers, let touchLocation = touches.first?.location(in: self)
{
processRotationGestureBegan(location: touchLocation)
}
}
if !_isRotating
{
super.nsuiTouchesBegan(touches, withEvent: event)
}
}
open override func nsuiTouchesMoved(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
{
if rotationEnabled && !rotationWithTwoFingers, let touch = touches.first
{
let touchLocation = touch.location(in: self)
processRotationGestureMoved(location: touchLocation)
}
if !_isRotating
{
super.nsuiTouchesMoved(touches, withEvent: event)
}
}
open override func nsuiTouchesEnded(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
{
if !_isRotating
{
super.nsuiTouchesEnded(touches, withEvent: event)
}
if rotationEnabled && !rotationWithTwoFingers, let touch = touches.first
{
let touchLocation = touch.location(in: self)
processRotationGestureEnded(location: touchLocation)
}
if _isRotating
{
_isRotating = false
}
}
open override func nsuiTouchesCancelled(_ touches: Set<NSUITouch>?, withEvent event: NSUIEvent?)
{
super.nsuiTouchesCancelled(touches, withEvent: event)
processRotationGestureCancelled()
}
#endif
#if os(OSX)
open override func mouseDown(with theEvent: NSEvent)
{
// if rotation by touch is enabled
if rotationEnabled
{
stopDeceleration()
let location = self.convert(theEvent.locationInWindow, from: nil)
processRotationGestureBegan(location: location)
}
if !_isRotating
{
super.mouseDown(with: theEvent)
}
}
open override func mouseDragged(with theEvent: NSEvent)
{
if rotationEnabled
{
let location = self.convert(theEvent.locationInWindow, from: nil)
processRotationGestureMoved(location: location)
}
if !_isRotating
{
super.mouseDragged(with: theEvent)
}
}
open override func mouseUp(with theEvent: NSEvent)
{
if !_isRotating
{
super.mouseUp(with: theEvent)
}
if rotationEnabled
{
let location = self.convert(theEvent.locationInWindow, from: nil)
processRotationGestureEnded(location: location)
}
if _isRotating
{
_isRotating = false
}
}
#endif
private func resetVelocity()
{
_velocitySamples.removeAll(keepingCapacity: false)
}
private func sampleVelocity(touchLocation: CGPoint)
{
let currentTime = CACurrentMediaTime()
_velocitySamples.append(AngularVelocitySample(time: currentTime, angle: angleForPoint(x: touchLocation.x, y: touchLocation.y)))
// Remove samples older than our sample time - 1 seconds
var i = 0, count = _velocitySamples.count
while (i < count - 2)
{
if currentTime - _velocitySamples[i].time > 1.0
{
_velocitySamples.remove(at: 0)
i -= 1
count -= 1
}
else
{
break
}
i += 1
}
}
private func calculateVelocity() -> CGFloat
{
if _velocitySamples.isEmpty
{
return 0.0
}
var firstSample = _velocitySamples[0]
var lastSample = _velocitySamples[_velocitySamples.count - 1]
// Look for a sample that's closest to the latest sample, but not the same, so we can deduce the direction
var beforeLastSample = firstSample
for i in stride(from: (_velocitySamples.count - 1), through: 0, by: -1)
{
beforeLastSample = _velocitySamples[i]
if beforeLastSample.angle != lastSample.angle
{
break
}
}
// Calculate the sampling time
var timeDelta = lastSample.time - firstSample.time
if timeDelta == 0.0
{
timeDelta = 0.1
}
// Calculate clockwise/ccw by choosing two values that should be closest to each other,
// so if the angles are two far from each other we know they are inverted "for sure"
var clockwise = lastSample.angle >= beforeLastSample.angle
if (abs(lastSample.angle - beforeLastSample.angle) > 270.0)
{
clockwise = !clockwise
}
// Now if the "gesture" is over a too big of an angle - then we know the angles are inverted, and we need to move them closer to each other from both sides of the 360.0 wrapping point
if lastSample.angle - firstSample.angle > 180.0
{
firstSample.angle += 360.0
}
else if firstSample.angle - lastSample.angle > 180.0
{
lastSample.angle += 360.0
}
// The velocity
var velocity = abs((lastSample.angle - firstSample.angle) / CGFloat(timeDelta))
// Direction?
if !clockwise
{
velocity = -velocity
}
return velocity
}
/// sets the starting angle of the rotation, this is only used by the touch listener, x and y is the touch position
private func setGestureStartAngle(x: CGFloat, y: CGFloat)
{
_startAngle = angleForPoint(x: x, y: y)
// take the current angle into consideration when starting a new drag
_startAngle -= _rotationAngle
}
/// updates the view rotation depending on the given touch position, also takes the starting angle into consideration
private func updateGestureRotation(x: CGFloat, y: CGFloat)
{
self.rotationAngle = angleForPoint(x: x, y: y) - _startAngle
}
@objc open func stopDeceleration()
{
if _decelerationDisplayLink !== nil
{
_decelerationDisplayLink.remove(from: RunLoop.main, forMode: RunLoopMode.commonModes)
_decelerationDisplayLink = nil
}
}
@objc private func decelerationLoop()
{
let currentTime = CACurrentMediaTime()
_decelerationAngularVelocity *= self.dragDecelerationFrictionCoef
let timeInterval = CGFloat(currentTime - _decelerationLastTime)
self.rotationAngle += _decelerationAngularVelocity * timeInterval
_decelerationLastTime = currentTime
if(abs(_decelerationAngularVelocity) < 0.001)
{
stopDeceleration()
}
}
/// - returns: The distance between two points
private func distance(eventX: CGFloat, startX: CGFloat, eventY: CGFloat, startY: CGFloat) -> CGFloat
{
let dx = eventX - startX
let dy = eventY - startY
return sqrt(dx * dx + dy * dy)
}
/// - returns: The distance between two points
private func distance(from: CGPoint, to: CGPoint) -> CGFloat
{
let dx = from.x - to.x
let dy = from.y - to.y
return sqrt(dx * dx + dy * dy)
}
/// reference to the last highlighted object
private var _lastHighlight: Highlight!
@objc private func tapGestureRecognized(_ recognizer: NSUITapGestureRecognizer)
{
if recognizer.state == NSUIGestureRecognizerState.ended
{
if !self.isHighLightPerTapEnabled { return }
let location = recognizer.location(in: self)
let high = self.getHighlightByTouchPoint(location)
self.highlightValue(high, callDelegate: true)
}
}
#if !os(tvOS)
@objc private func rotationGestureRecognized(_ recognizer: NSUIRotationGestureRecognizer)
{
if recognizer.state == NSUIGestureRecognizerState.began
{
stopDeceleration()
_startAngle = self.rawRotationAngle
}
if recognizer.state == NSUIGestureRecognizerState.began || recognizer.state == NSUIGestureRecognizerState.changed
{
let angle = recognizer.nsuiRotation.RAD2DEG
self.rotationAngle = _startAngle + angle
setNeedsDisplay()
}
else if recognizer.state == NSUIGestureRecognizerState.ended
{
let angle = recognizer.nsuiRotation.RAD2DEG
self.rotationAngle = _startAngle + angle
setNeedsDisplay()
if isDragDecelerationEnabled
{
stopDeceleration()
_decelerationAngularVelocity = recognizer.velocity.RAD2DEG
if _decelerationAngularVelocity != 0.0
{
_decelerationLastTime = CACurrentMediaTime()
_decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(PieRadarChartViewBase.decelerationLoop))
_decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
}
}
}
}
#endif
}

View File

@@ -0,0 +1,232 @@
//
// RadarChartView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// Implementation of the RadarChart, a "spidernet"-like chart. It works best
/// when displaying 5-10 entries per DataSet.
open class RadarChartView: PieRadarChartViewBase
{
/// width of the web lines that come from the center.
@objc open var webLineWidth = CGFloat(1.5)
/// width of the web lines that are in between the lines coming from the center
@objc open var innerWebLineWidth = CGFloat(0.75)
/// color for the web lines that come from the center
@objc open var webColor = NSUIColor(red: 122/255.0, green: 122/255.0, blue: 122.0/255.0, alpha: 1.0)
/// color for the web lines in between the lines that come from the center.
@objc open var innerWebColor = NSUIColor(red: 122/255.0, green: 122/255.0, blue: 122.0/255.0, alpha: 1.0)
/// transparency the grid is drawn with (0.0 - 1.0)
@objc open var webAlpha: CGFloat = 150.0 / 255.0
/// flag indicating if the web lines should be drawn or not
@objc open var drawWeb = true
/// modulus that determines how many labels and web-lines are skipped before the next is drawn
private var _skipWebLineCount = 0
/// the object reprsenting the y-axis labels
private var _yAxis: YAxis!
internal var _yAxisRenderer: YAxisRendererRadarChart!
internal var _xAxisRenderer: XAxisRendererRadarChart!
public override init(frame: CGRect)
{
super.init(frame: frame)
}
public required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
internal override func initialize()
{
super.initialize()
_yAxis = YAxis(position: .left)
renderer = RadarChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler)
_yAxisRenderer = YAxisRendererRadarChart(viewPortHandler: _viewPortHandler, yAxis: _yAxis, chart: self)
_xAxisRenderer = XAxisRendererRadarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, chart: self)
self.highlighter = RadarHighlighter(chart: self)
}
internal override func calcMinMax()
{
super.calcMinMax()
guard let data = _data else { return }
_yAxis.calculate(min: data.getYMin(axis: .left), max: data.getYMax(axis: .left))
_xAxis.calculate(min: 0.0, max: Double(data.maxEntryCountSet?.entryCount ?? 0))
}
open override func notifyDataSetChanged()
{
calcMinMax()
_yAxisRenderer?.computeAxis(min: _yAxis._axisMinimum, max: _yAxis._axisMaximum, inverted: _yAxis.isInverted)
_xAxisRenderer?.computeAxis(min: _xAxis._axisMinimum, max: _xAxis._axisMaximum, inverted: false)
if let data = _data,
let legend = _legend,
!legend.isLegendCustom
{
legendRenderer?.computeLegend(data: data)
}
calculateOffsets()
setNeedsDisplay()
}
open override func draw(_ rect: CGRect)
{
super.draw(rect)
guard data != nil, let renderer = renderer else { return }
let optionalContext = NSUIGraphicsGetCurrentContext()
guard let context = optionalContext else { return }
if _xAxis.isEnabled
{
_xAxisRenderer.computeAxis(min: _xAxis._axisMinimum, max: _xAxis._axisMaximum, inverted: false)
}
_xAxisRenderer?.renderAxisLabels(context: context)
if drawWeb
{
renderer.drawExtras(context: context)
}
if _yAxis.isEnabled && _yAxis.isDrawLimitLinesBehindDataEnabled
{
_yAxisRenderer.renderLimitLines(context: context)
}
renderer.drawData(context: context)
if valuesToHighlight()
{
renderer.drawHighlighted(context: context, indices: _indicesToHighlight)
}
if _yAxis.isEnabled && !_yAxis.isDrawLimitLinesBehindDataEnabled
{
_yAxisRenderer.renderLimitLines(context: context)
}
_yAxisRenderer.renderAxisLabels(context: context)
renderer.drawValues(context: context)
legendRenderer.renderLegend(context: context)
drawDescription(context: context)
drawMarkers(context: context)
}
/// - returns: The factor that is needed to transform values into pixels.
@objc open var factor: CGFloat
{
let content = _viewPortHandler.contentRect
return min(content.width / 2.0, content.height / 2.0)
/ CGFloat(_yAxis.axisRange)
}
/// - returns: The angle that each slice in the radar chart occupies.
@objc open var sliceAngle: CGFloat
{
return 360.0 / CGFloat(_data?.maxEntryCountSet?.entryCount ?? 0)
}
open override func indexForAngle(_ angle: CGFloat) -> Int
{
// take the current angle of the chart into consideration
let a = (angle - self.rotationAngle).normalizedAngle
let sliceAngle = self.sliceAngle
let max = _data?.maxEntryCountSet?.entryCount ?? 0
var index = 0
for i in 0..<max
{
let referenceAngle = sliceAngle * CGFloat(i + 1) - sliceAngle / 2.0
if referenceAngle > a
{
index = i
break
}
}
return index
}
/// - returns: The object that represents all y-labels of the RadarChart.
@objc open var yAxis: YAxis
{
return _yAxis
}
/// Sets the number of web-lines that should be skipped on chart web before the next one is drawn. This targets the lines that come from the center of the RadarChart.
/// if count = 1 -> 1 line is skipped in between
@objc open var skipWebLineCount: Int
{
get
{
return _skipWebLineCount
}
set
{
_skipWebLineCount = max(0, newValue)
}
}
internal override var requiredLegendOffset: CGFloat
{
return _legend.font.pointSize * 4.0
}
internal override var requiredBaseOffset: CGFloat
{
return _xAxis.isEnabled && _xAxis.isDrawLabelsEnabled ? _xAxis.labelRotatedWidth : 10.0
}
open override var radius: CGFloat
{
let content = _viewPortHandler.contentRect
return min(content.width / 2.0, content.height / 2.0)
}
/// - returns: The maximum value this chart can display on it's y-axis.
open override var chartYMax: Double { return _yAxis._axisMaximum }
/// - returns: The minimum value this chart can display on it's y-axis.
open override var chartYMin: Double { return _yAxis._axisMinimum }
/// - returns: The range of y-values this chart can display.
@objc open var yRange: Double { return _yAxis.axisRange }
}

View File

@@ -0,0 +1,31 @@
//
// ScatterChartView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// The ScatterChart. Draws dots, triangles, squares and custom shapes into the chartview.
open class ScatterChartView: BarLineChartViewBase, ScatterChartDataProvider
{
open override func initialize()
{
super.initialize()
renderer = ScatterChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler)
xAxis.spaceMin = 0.5
xAxis.spaceMax = 0.5
}
// MARK: - ScatterChartDataProvider
open var scatterData: ScatterChartData? { return _data as? ScatterChartData }
}

View File

@@ -0,0 +1,371 @@
//
// AxisBase.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// Base class for all axes
@objc(ChartAxisBase)
open class AxisBase: ComponentBase
{
public override init()
{
super.init()
}
/// Custom formatter that is used instead of the auto-formatter if set
private var _axisValueFormatter: IAxisValueFormatter?
@objc open var labelFont = NSUIFont.systemFont(ofSize: 10.0)
@objc open var labelTextColor = NSUIColor.black
@objc open var axisLineColor = NSUIColor.gray
@objc open var axisLineWidth = CGFloat(0.5)
@objc open var axisLineDashPhase = CGFloat(0.0)
@objc open var axisLineDashLengths: [CGFloat]!
@objc open var gridColor = NSUIColor.gray.withAlphaComponent(0.9)
@objc open var gridLineWidth = CGFloat(0.5)
@objc open var gridLineDashPhase = CGFloat(0.0)
@objc open var gridLineDashLengths: [CGFloat]!
@objc open var gridLineCap = CGLineCap.butt
@objc open var drawGridLinesEnabled = true
@objc open var drawAxisLineEnabled = true
/// flag that indicates of the labels of this axis should be drawn or not
@objc open var drawLabelsEnabled = true
private var _centerAxisLabelsEnabled = false
/// Centers the axis labels instead of drawing them at their original position.
/// This is useful especially for grouped BarChart.
@objc open var centerAxisLabelsEnabled: Bool
{
get { return _centerAxisLabelsEnabled && entryCount > 0 }
set { _centerAxisLabelsEnabled = newValue }
}
@objc open var isCenterAxisLabelsEnabled: Bool
{
get { return centerAxisLabelsEnabled }
}
/// array of limitlines that can be set for the axis
private var _limitLines = [ChartLimitLine]()
/// Are the LimitLines drawn behind the data or in front of the data?
///
/// **default**: false
@objc open var drawLimitLinesBehindDataEnabled = false
/// the flag can be used to turn off the antialias for grid lines
@objc open var gridAntialiasEnabled = true
/// the actual array of entries
@objc open var entries = [Double]()
/// axis label entries only used for centered labels
@objc open var centeredEntries = [Double]()
/// the number of entries the legend contains
@objc open var entryCount: Int { return entries.count }
/// the number of label entries the axis should have
///
/// **default**: 6
private var _labelCount = Int(6)
/// the number of decimal digits to use (for the default formatter
@objc open var decimals: Int = 0
/// When true, axis labels are controlled by the `granularity` property.
/// When false, axis values could possibly be repeated.
/// This could happen if two adjacent axis values are rounded to same value.
/// If using granularity this could be avoided by having fewer axis values visible.
@objc open var granularityEnabled = false
private var _granularity = Double(1.0)
/// The minimum interval between axis values.
/// This can be used to avoid label duplicating when zooming in.
///
/// **default**: 1.0
@objc open var granularity: Double
{
get
{
return _granularity
}
set
{
_granularity = newValue
// set this to `true` if it was disabled, as it makes no sense to set this property with granularity disabled
granularityEnabled = true
}
}
/// The minimum interval between axis values.
@objc open var isGranularityEnabled: Bool
{
get
{
return granularityEnabled
}
}
/// if true, the set number of y-labels will be forced
@objc open var forceLabelsEnabled = false
@objc open func getLongestLabel() -> String
{
var longest = ""
for i in 0 ..< entries.count
{
let text = getFormattedLabel(i)
if longest.count < text.count
{
longest = text
}
}
return longest
}
/// - returns: The formatted label at the specified index. This will either use the auto-formatter or the custom formatter (if one is set).
@objc open func getFormattedLabel(_ index: Int) -> String
{
if index < 0 || index >= entries.count
{
return ""
}
return valueFormatter?.stringForValue(entries[index], axis: self) ?? ""
}
/// Sets the formatter to be used for formatting the axis labels.
/// If no formatter is set, the chart will automatically determine a reasonable formatting (concerning decimals) for all the values that are drawn inside the chart.
/// Use `nil` to use the formatter calculated by the chart.
@objc open var valueFormatter: IAxisValueFormatter?
{
get
{
if _axisValueFormatter == nil ||
(_axisValueFormatter is DefaultAxisValueFormatter &&
(_axisValueFormatter as! DefaultAxisValueFormatter).hasAutoDecimals &&
(_axisValueFormatter as! DefaultAxisValueFormatter).decimals != decimals)
{
_axisValueFormatter = DefaultAxisValueFormatter(decimals: decimals)
}
return _axisValueFormatter
}
set
{
_axisValueFormatter = newValue ?? DefaultAxisValueFormatter(decimals: decimals)
}
}
@objc open var isDrawGridLinesEnabled: Bool { return drawGridLinesEnabled }
@objc open var isDrawAxisLineEnabled: Bool { return drawAxisLineEnabled }
@objc open var isDrawLabelsEnabled: Bool { return drawLabelsEnabled }
/// Are the LimitLines drawn behind the data or in front of the data?
///
/// **default**: false
@objc open var isDrawLimitLinesBehindDataEnabled: Bool { return drawLimitLinesBehindDataEnabled }
/// Extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum`
@objc open var spaceMin: Double = 0.0
/// Extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum`
@objc open var spaceMax: Double = 0.0
/// Flag indicating that the axis-min value has been customized
internal var _customAxisMin: Bool = false
/// Flag indicating that the axis-max value has been customized
internal var _customAxisMax: Bool = false
/// Do not touch this directly, instead, use axisMinimum.
/// This is automatically calculated to represent the real min value,
/// and is used when calculating the effective minimum.
internal var _axisMinimum = Double(0)
/// Do not touch this directly, instead, use axisMaximum.
/// This is automatically calculated to represent the real max value,
/// and is used when calculating the effective maximum.
internal var _axisMaximum = Double(0)
/// the total range of values this axis covers
@objc open var axisRange = Double(0)
/// The minumum number of labels on the axis
@objc open var axisMinLabels = Int(2) {
didSet { axisMinLabels = axisMinLabels > 0 ? axisMinLabels : oldValue }
}
/// The maximum number of labels on the axis
@objc open var axisMaxLabels = Int(25) {
didSet { axisMinLabels = axisMaxLabels > 0 ? axisMaxLabels : oldValue }
}
/// the number of label entries the axis should have
/// max = 25,
/// min = 2,
/// default = 6,
/// be aware that this number is not fixed and can only be approximated
@objc open var labelCount: Int
{
get
{
return _labelCount
}
set
{
_labelCount = newValue
if _labelCount > axisMaxLabels
{
_labelCount = axisMaxLabels
}
if _labelCount < axisMinLabels
{
_labelCount = axisMinLabels
}
forceLabelsEnabled = false
}
}
@objc open func setLabelCount(_ count: Int, force: Bool)
{
self.labelCount = count
forceLabelsEnabled = force
}
/// - returns: `true` if focing the y-label count is enabled. Default: false
@objc open var isForceLabelsEnabled: Bool { return forceLabelsEnabled }
/// Adds a new ChartLimitLine to this axis.
@objc open func addLimitLine(_ line: ChartLimitLine)
{
_limitLines.append(line)
}
/// Removes the specified ChartLimitLine from the axis.
@objc open func removeLimitLine(_ line: ChartLimitLine)
{
for i in 0 ..< _limitLines.count
{
if _limitLines[i] === line
{
_limitLines.remove(at: i)
return
}
}
}
/// Removes all LimitLines from the axis.
@objc open func removeAllLimitLines()
{
_limitLines.removeAll(keepingCapacity: false)
}
/// - returns: The LimitLines of this axis.
@objc open var limitLines : [ChartLimitLine]
{
return _limitLines
}
// MARK: Custom axis ranges
/// By calling this method, any custom minimum value that has been previously set is reseted, and the calculation is done automatically.
@objc open func resetCustomAxisMin()
{
_customAxisMin = false
}
@objc open var isAxisMinCustom: Bool { return _customAxisMin }
/// By calling this method, any custom maximum value that has been previously set is reseted, and the calculation is done automatically.
@objc open func resetCustomAxisMax()
{
_customAxisMax = false
}
@objc open var isAxisMaxCustom: Bool { return _customAxisMax }
/// The minimum value for this axis.
/// If set, this value will not be calculated automatically depending on the provided data.
/// Use `resetCustomAxisMin()` to undo this.
@objc open var axisMinimum: Double
{
get
{
return _axisMinimum
}
set
{
_customAxisMin = true
_axisMinimum = newValue
axisRange = abs(_axisMaximum - newValue)
}
}
/// The maximum value for this axis.
/// If set, this value will not be calculated automatically depending on the provided data.
/// Use `resetCustomAxisMax()` to undo this.
@objc open var axisMaximum: Double
{
get
{
return _axisMaximum
}
set
{
_customAxisMax = true
_axisMaximum = newValue
axisRange = abs(newValue - _axisMinimum)
}
}
/// Calculates the minimum, maximum and range values of the YAxis with the given minimum and maximum values from the chart data.
/// - parameter dataMin: the y-min value according to chart data
/// - parameter dataMax: the y-max value according to chart
@objc open func calculate(min dataMin: Double, max dataMax: Double)
{
// if custom, use value as is, else use data value
var min = _customAxisMin ? _axisMinimum : (dataMin - spaceMin)
var max = _customAxisMax ? _axisMaximum : (dataMax + spaceMax)
// temporary range (before calculations)
let range = abs(max - min)
// in case all values are equal
if range == 0.0
{
max = max + 1.0
min = min - 1.0
}
_axisMinimum = min
_axisMaximum = max
// actual range
axisRange = abs(max - min)
}
}

View File

@@ -0,0 +1,85 @@
//
// ChartLimitLine.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// The limit line is an additional feature for all Line, Bar and ScatterCharts.
/// It allows the displaying of an additional line in the chart that marks a certain maximum / limit on the specified axis (x- or y-axis).
open class ChartLimitLine: ComponentBase
{
@objc(ChartLimitLabelPosition)
public enum LabelPosition: Int
{
case leftTop
case leftBottom
case rightTop
case rightBottom
}
/// limit / maximum (the y-value or xIndex)
@objc open var limit = Double(0.0)
private var _lineWidth = CGFloat(2.0)
@objc open var lineColor = NSUIColor(red: 237.0/255.0, green: 91.0/255.0, blue: 91.0/255.0, alpha: 1.0)
@objc open var lineDashPhase = CGFloat(0.0)
@objc open var lineDashLengths: [CGFloat]?
@objc open var valueTextColor = NSUIColor.black
@objc open var valueFont = NSUIFont.systemFont(ofSize: 13.0)
@objc open var drawLabelEnabled = true
@objc open var label = ""
@objc open var labelPosition = LabelPosition.rightTop
public override init()
{
super.init()
}
@objc public init(limit: Double)
{
super.init()
self.limit = limit
}
@objc public init(limit: Double, label: String)
{
super.init()
self.limit = limit
self.label = label
}
/// set the line width of the chart (min = 0.2, max = 12); default 2
@objc open var lineWidth: CGFloat
{
get
{
return _lineWidth
}
set
{
if newValue < 0.2
{
_lineWidth = 0.2
}
else if newValue > 12.0
{
_lineWidth = 12.0
}
else
{
_lineWidth = newValue
}
}
}
}

View File

@@ -0,0 +1,37 @@
//
// ComponentBase.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// This class encapsulates everything both Axis, Legend and LimitLines have in common
@objc(ChartComponentBase)
open class ComponentBase: NSObject
{
/// flag that indicates if this component is enabled or not
@objc open var enabled = true
/// The offset this component has on the x-axis
/// **default**: 5.0
@objc open var xOffset = CGFloat(5.0)
/// The offset this component has on the x-axis
/// **default**: 5.0 (or 0.0 on ChartYAxis)
@objc open var yOffset = CGFloat(5.0)
public override init()
{
super.init()
}
@objc open var isEnabled: Bool { return enabled }
}

View File

@@ -0,0 +1,50 @@
//
// Description.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc(ChartDescription)
open class Description: ComponentBase
{
public override init()
{
#if os(tvOS)
// 23 is the smallest recommended font size on the TV
font = NSUIFont.systemFont(ofSize: 23)
#elseif os(OSX)
font = NSUIFont.systemFont(ofSize: NSUIFont.systemFontSize)
#else
font = NSUIFont.systemFont(ofSize: 8.0)
#endif
super.init()
}
/// The text to be shown as the description.
@objc open var text: String? = "Description Label"
/// Custom position for the description text in pixels on the screen.
open var position: CGPoint? = nil
/// The text alignment of the description text. Default RIGHT.
@objc open var textAlign: NSTextAlignment = NSTextAlignment.right
/// Font object used for drawing the description text.
@objc open var font: NSUIFont
/// Text color used for drawing the description text
@objc open var textColor = NSUIColor.black
}

View File

@@ -0,0 +1,40 @@
//
// ChartMarker.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(IChartMarker)
public protocol IMarker: class
{
/// - returns: The desired (general) offset you wish the IMarker to have on the x-axis.
///
/// By returning x: -(width / 2) you will center the IMarker horizontally.
///
/// By returning y: -(height / 2) you will center the IMarker vertically.
var offset: CGPoint { get }
/// - returns: The offset for drawing at the specific `point`.
/// This allows conditional adjusting of the Marker position.
/// If you have no adjustments to make, return self.offset().
///
/// - parameter point: This is the point at which the marker wants to be drawn. You can adjust the offset conditionally based on this argument.
func offsetForDrawing(atPoint: CGPoint) -> CGPoint
/// This method enables a custom IMarker to update it's content every time the IMarker is redrawn according to the data entry it points to.
///
/// - parameter entry: The Entry the IMarker belongs to. This can also be any subclass of Entry, like BarEntry or CandleEntry, simply cast it at runtime.
/// - parameter highlight: The highlight object contains information about the highlighted value such as it's dataset-index, the selected range or stack-index (only stacked bar entries).
func refreshContent(entry: ChartDataEntry, highlight: Highlight)
/// Draws the IMarker on the given position on the given context
func draw(context: CGContext, point: CGPoint)
}

View File

@@ -0,0 +1,428 @@
//
// Legend.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc(ChartLegend)
open class Legend: ComponentBase
{
@objc(ChartLegendForm)
public enum Form: Int
{
/// Avoid drawing a form
case none
/// Do not draw the a form, but leave space for it
case empty
/// Use default (default dataset's form to the legend's form)
case `default`
/// Draw a square
case square
/// Draw a circle
case circle
/// Draw a horizontal line
case line
}
@objc(ChartLegendHorizontalAlignment)
public enum HorizontalAlignment: Int
{
case left
case center
case right
}
@objc(ChartLegendVerticalAlignment)
public enum VerticalAlignment: Int
{
case top
case center
case bottom
}
@objc(ChartLegendOrientation)
public enum Orientation: Int
{
case horizontal
case vertical
}
@objc(ChartLegendDirection)
public enum Direction: Int
{
case leftToRight
case rightToLeft
}
/// The legend entries array
@objc open var entries = [LegendEntry]()
/// Entries that will be appended to the end of the auto calculated entries after calculating the legend.
/// (if the legend has already been calculated, you will need to call notifyDataSetChanged() to let the changes take effect)
@objc open var extraEntries = [LegendEntry]()
/// Are the legend labels/colors a custom value or auto calculated? If false, then it's auto, if true, then custom.
///
/// **default**: false (automatic legend)
private var _isLegendCustom = false
/// The horizontal alignment of the legend
@objc open var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.left
/// The vertical alignment of the legend
@objc open var verticalAlignment: VerticalAlignment = VerticalAlignment.bottom
/// The orientation of the legend
@objc open var orientation: Orientation = Orientation.horizontal
/// Flag indicating whether the legend will draw inside the chart or outside
@objc open var drawInside: Bool = false
/// Flag indicating whether the legend will draw inside the chart or outside
@objc open var isDrawInsideEnabled: Bool { return drawInside }
/// The text direction of the legend
@objc open var direction: Direction = Direction.leftToRight
@objc open var font: NSUIFont = NSUIFont.systemFont(ofSize: 10.0)
@objc open var textColor = NSUIColor.black
/// The form/shape of the legend forms
@objc open var form = Form.square
/// The size of the legend forms
@objc open var formSize = CGFloat(8.0)
/// The line width for forms that consist of lines
@objc open var formLineWidth = CGFloat(3.0)
/// Line dash configuration for shapes that consist of lines.
///
/// This is how much (in pixels) into the dash pattern are we starting from.
@objc open var formLineDashPhase: CGFloat = 0.0
/// Line dash configuration for shapes that consist of lines.
///
/// This is the actual dash pattern.
/// I.e. [2, 3] will paint [-- -- ]
/// [1, 3, 4, 2] will paint [- ---- - ---- ]
@objc open var formLineDashLengths: [CGFloat]?
@objc open var xEntrySpace = CGFloat(6.0)
@objc open var yEntrySpace = CGFloat(0.0)
@objc open var formToTextSpace = CGFloat(5.0)
@objc open var stackSpace = CGFloat(3.0)
@objc open var calculatedLabelSizes = [CGSize]()
@objc open var calculatedLabelBreakPoints = [Bool]()
@objc open var calculatedLineSizes = [CGSize]()
public override init()
{
super.init()
self.xOffset = 5.0
self.yOffset = 3.0
}
@objc public init(entries: [LegendEntry])
{
super.init()
self.entries = entries
}
@objc open func getMaximumEntrySize(withFont font: NSUIFont) -> CGSize
{
var maxW = CGFloat(0.0)
var maxH = CGFloat(0.0)
var maxFormSize: CGFloat = 0.0
for entry in entries
{
let formSize = entry.formSize.isNaN ? self.formSize : entry.formSize
if formSize > maxFormSize
{
maxFormSize = formSize
}
guard let label = entry.label
else { continue }
let size = (label as NSString).size(withAttributes: [.font: font])
if size.width > maxW
{
maxW = size.width
}
if size.height > maxH
{
maxH = size.height
}
}
return CGSize(
width: maxW + maxFormSize + formToTextSpace,
height: maxH
)
}
@objc open var neededWidth = CGFloat(0.0)
@objc open var neededHeight = CGFloat(0.0)
@objc open var textWidthMax = CGFloat(0.0)
@objc open var textHeightMax = CGFloat(0.0)
/// flag that indicates if word wrapping is enabled
/// this is currently supported only for `orientation == Horizontal`.
/// you may want to set maxSizePercent when word wrapping, to set the point where the text wraps.
///
/// **default**: true
@objc open var wordWrapEnabled = true
/// if this is set, then word wrapping the legend is enabled.
@objc open var isWordWrapEnabled: Bool { return wordWrapEnabled }
/// The maximum relative size out of the whole chart view in percent.
/// If the legend is to the right/left of the chart, then this affects the width of the legend.
/// If the legend is to the top/bottom of the chart, then this affects the height of the legend.
///
/// **default**: 0.95 (95%)
@objc open var maxSizePercent: CGFloat = 0.95
@objc open func calculateDimensions(labelFont: NSUIFont, viewPortHandler: ViewPortHandler)
{
let maxEntrySize = getMaximumEntrySize(withFont: labelFont)
let defaultFormSize = self.formSize
let stackSpace = self.stackSpace
let formToTextSpace = self.formToTextSpace
let xEntrySpace = self.xEntrySpace
let yEntrySpace = self.yEntrySpace
let wordWrapEnabled = self.wordWrapEnabled
let entries = self.entries
let entryCount = entries.count
textWidthMax = maxEntrySize.width
textHeightMax = maxEntrySize.height
switch orientation
{
case .vertical:
var maxWidth = CGFloat(0.0)
var width = CGFloat(0.0)
var maxHeight = CGFloat(0.0)
let labelLineHeight = labelFont.lineHeight
var wasStacked = false
for i in 0 ..< entryCount
{
let e = entries[i]
let drawingForm = e.form != .none
let formSize = e.formSize.isNaN ? defaultFormSize : e.formSize
let label = e.label
if !wasStacked
{
width = 0.0
}
if drawingForm
{
if wasStacked
{
width += stackSpace
}
width += formSize
}
if label != nil
{
let size = (label! as NSString).size(withAttributes: [.font: labelFont])
if drawingForm && !wasStacked
{
width += formToTextSpace
}
else if wasStacked
{
maxWidth = max(maxWidth, width)
maxHeight += labelLineHeight + yEntrySpace
width = 0.0
wasStacked = false
}
width += size.width
if i < entryCount - 1
{
maxHeight += labelLineHeight + yEntrySpace
}
}
else
{
wasStacked = true
width += formSize
if i < entryCount - 1
{
width += stackSpace
}
}
maxWidth = max(maxWidth, width)
}
neededWidth = maxWidth
neededHeight = maxHeight
case .horizontal:
let labelLineHeight = labelFont.lineHeight
let contentWidth: CGFloat = viewPortHandler.contentWidth * maxSizePercent
// Prepare arrays for calculated layout
if calculatedLabelSizes.count != entryCount
{
calculatedLabelSizes = [CGSize](repeating: CGSize(), count: entryCount)
}
if calculatedLabelBreakPoints.count != entryCount
{
calculatedLabelBreakPoints = [Bool](repeating: false, count: entryCount)
}
calculatedLineSizes.removeAll(keepingCapacity: true)
// Start calculating layout
let labelAttrs = [NSAttributedStringKey.font: labelFont]
var maxLineWidth: CGFloat = 0.0
var currentLineWidth: CGFloat = 0.0
var requiredWidth: CGFloat = 0.0
var stackedStartIndex: Int = -1
for i in 0 ..< entryCount
{
let e = entries[i]
let drawingForm = e.form != .none
let label = e.label
calculatedLabelBreakPoints[i] = false
if stackedStartIndex == -1
{
// we are not stacking, so required width is for this label only
requiredWidth = 0.0
}
else
{
// add the spacing appropriate for stacked labels/forms
requiredWidth += stackSpace
}
// grouped forms have null labels
if label != nil
{
calculatedLabelSizes[i] = (label! as NSString).size(withAttributes: labelAttrs)
requiredWidth += drawingForm ? formToTextSpace + formSize : 0.0
requiredWidth += calculatedLabelSizes[i].width
}
else
{
calculatedLabelSizes[i] = CGSize()
requiredWidth += drawingForm ? formSize : 0.0
if stackedStartIndex == -1
{
// mark this index as we might want to break here later
stackedStartIndex = i
}
}
if label != nil || i == entryCount - 1
{
let requiredSpacing = currentLineWidth == 0.0 ? 0.0 : xEntrySpace
if (!wordWrapEnabled || // No word wrapping, it must fit.
currentLineWidth == 0.0 || // The line is empty, it must fit.
(contentWidth - currentLineWidth >= requiredSpacing + requiredWidth)) // It simply fits
{
// Expand current line
currentLineWidth += requiredSpacing + requiredWidth
}
else
{ // It doesn't fit, we need to wrap a line
// Add current line size to array
calculatedLineSizes.append(CGSize(width: currentLineWidth, height: labelLineHeight))
maxLineWidth = max(maxLineWidth, currentLineWidth)
// Start a new line
calculatedLabelBreakPoints[stackedStartIndex > -1 ? stackedStartIndex : i] = true
currentLineWidth = requiredWidth
}
if i == entryCount - 1
{ // Add last line size to array
calculatedLineSizes.append(CGSize(width: currentLineWidth, height: labelLineHeight))
maxLineWidth = max(maxLineWidth, currentLineWidth)
}
}
stackedStartIndex = label != nil ? -1 : stackedStartIndex
}
neededWidth = maxLineWidth
neededHeight = labelLineHeight * CGFloat(calculatedLineSizes.count) +
yEntrySpace * CGFloat(calculatedLineSizes.count == 0 ? 0 : (calculatedLineSizes.count - 1))
}
neededWidth += xOffset
neededHeight += yOffset
}
/// MARK: - Custom legend
/// Sets a custom legend's entries array.
/// * A nil label will start a group.
/// This will disable the feature that automatically calculates the legend entries from the datasets.
/// Call `resetCustom(...)` to re-enable automatic calculation (and then `notifyDataSetChanged()` is needed).
@objc open func setCustom(entries: [LegendEntry])
{
self.entries = entries
_isLegendCustom = true
}
/// Calling this will disable the custom legend entries (set by `setLegend(...)`). Instead, the entries will again be calculated automatically (after `notifyDataSetChanged()` is called).
@objc open func resetCustom()
{
_isLegendCustom = false
}
/// **default**: false (automatic legend)
/// - returns: `true` if a custom legend entries has been set
@objc open var isLegendCustom: Bool
{
return _isLegendCustom
}
}

View File

@@ -0,0 +1,91 @@
//
// LegendEntry.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc(ChartLegendEntry)
open class LegendEntry: NSObject
{
public override init()
{
super.init()
}
/// - parameter label: The legend entry text.
/// A `nil` label will start a group.
/// - parameter form: The form to draw for this entry.
/// - parameter formSize: Set to NaN to use the legend's default.
/// - parameter formLineWidth: Set to NaN to use the legend's default.
/// - parameter formLineDashPhase: Line dash configuration.
/// - parameter formLineDashLengths: Line dash configurationas NaN to use the legend's default.
/// - parameter formColor: The color for drawing the form.
@objc public init(label: String?,
form: Legend.Form,
formSize: CGFloat,
formLineWidth: CGFloat,
formLineDashPhase: CGFloat,
formLineDashLengths: [CGFloat]?,
formColor: NSUIColor?)
{
self.label = label
self.form = form
self.formSize = formSize
self.formLineWidth = formLineWidth
self.formLineDashPhase = formLineDashPhase
self.formLineDashLengths = formLineDashLengths
self.formColor = formColor
}
/// The legend entry text.
/// A `nil` label will start a group.
@objc open var label: String?
/// The form to draw for this entry.
///
/// `None` will avoid drawing a form, and any related space.
/// `Empty` will avoid drawing a form, but keep its space.
/// `Default` will use the Legend's default.
@objc open var form: Legend.Form = .default
/// Form size will be considered except for when .None is used
///
/// Set as NaN to use the legend's default
@objc open var formSize: CGFloat = CGFloat.nan
/// Line width used for shapes that consist of lines.
///
/// Set to NaN to use the legend's default.
@objc open var formLineWidth: CGFloat = CGFloat.nan
/// Line dash configuration for shapes that consist of lines.
///
/// This is how much (in pixels) into the dash pattern are we starting from.
///
/// Set to NaN to use the legend's default.
@objc open var formLineDashPhase: CGFloat = 0.0
/// Line dash configuration for shapes that consist of lines.
///
/// This is the actual dash pattern.
/// I.e. [2, 3] will paint [-- -- ]
/// [1, 3, 4, 2] will paint [- ---- - ---- ]
///
/// Set to nil to use the legend's default.
@objc open var formLineDashLengths: [CGFloat]?
/// The color for drawing the form
@objc open var formColor: NSUIColor?
}

View File

@@ -0,0 +1,110 @@
//
// ChartMarkerImage.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc(ChartMarkerImage)
open class MarkerImage: NSObject, IMarker
{
/// The marker image to render
@objc open var image: NSUIImage?
open var offset: CGPoint = CGPoint()
@objc open weak var chartView: ChartViewBase?
/// As long as size is 0.0/0.0 - it will default to the image's size
@objc open var size: CGSize = CGSize()
public override init()
{
super.init()
}
open func offsetForDrawing(atPoint point: CGPoint) -> CGPoint
{
var offset = self.offset
let chart = self.chartView
var size = self.size
if size.width == 0.0 && image != nil
{
size.width = image?.size.width ?? 0.0
}
if size.height == 0.0 && image != nil
{
size.height = image?.size.height ?? 0.0
}
let width = size.width
let height = size.height
if point.x + offset.x < 0.0
{
offset.x = -point.x
}
else if chart != nil && point.x + width + offset.x > chart!.bounds.size.width
{
offset.x = chart!.bounds.size.width - point.x - width
}
if point.y + offset.y < 0
{
offset.y = -point.y
}
else if chart != nil && point.y + height + offset.y > chart!.bounds.size.height
{
offset.y = chart!.bounds.size.height - point.y - height
}
return offset
}
open func refreshContent(entry: ChartDataEntry, highlight: Highlight)
{
// Do nothing here...
}
open func draw(context: CGContext, point: CGPoint)
{
guard let image = image else { return }
let offset = offsetForDrawing(atPoint: point)
var size = self.size
if size.width == 0.0
{
size.width = image.size.width
}
if size.height == 0.0
{
size.height = image.size.height
}
let rect = CGRect(
x: point.x + offset.x,
y: point.y + offset.y,
width: size.width,
height: size.height)
NSUIGraphicsPushContext(context)
image.draw(in: rect)
NSUIGraphicsPopContext()
}
}

View File

@@ -0,0 +1,99 @@
//
// ChartMarkerView.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc(ChartMarkerView)
open class MarkerView: NSUIView, IMarker
{
open var offset: CGPoint = CGPoint()
@objc open weak var chartView: ChartViewBase?
open func offsetForDrawing(atPoint point: CGPoint) -> CGPoint
{
guard let chart = chartView else { return self.offset }
var offset = self.offset
let width = self.bounds.size.width
let height = self.bounds.size.height
if point.x + offset.x < 0.0
{
offset.x = -point.x
}
else if point.x + width + offset.x > chart.bounds.size.width
{
offset.x = chart.bounds.size.width - point.x - width
}
if point.y + offset.y < 0
{
offset.y = -point.y
}
else if point.y + height + offset.y > chart.bounds.size.height
{
offset.y = chart.bounds.size.height - point.y - height
}
return offset
}
open func refreshContent(entry: ChartDataEntry, highlight: Highlight)
{
// Do nothing here...
}
open func draw(context: CGContext, point: CGPoint)
{
let offset = self.offsetForDrawing(atPoint: point)
context.saveGState()
context.translateBy(x: point.x + offset.x,
y: point.y + offset.y)
NSUIGraphicsPushContext(context)
self.nsuiLayer?.render(in: context)
NSUIGraphicsPopContext()
context.restoreGState()
}
@objc
open class func viewFromXib(in bundle: Bundle = .main) -> MarkerView?
{
#if !os(OSX)
return bundle.loadNibNamed(
String(describing: self),
owner: nil,
options: nil)?[0] as? MarkerView
#else
var loadedObjects = NSArray()
let loadedObjectsPointer = AutoreleasingUnsafeMutablePointer<NSArray?>(&loadedObjects)
if bundle.loadNibNamed(
NSNib.Name(String(describing: self)),
owner: nil,
topLevelObjects: loadedObjectsPointer)
{
return loadedObjects[0] as? MarkerView
}
return nil
#endif
}
}

View File

@@ -0,0 +1,75 @@
//
// XAxis.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(ChartXAxis)
open class XAxis: AxisBase
{
@objc(XAxisLabelPosition)
public enum LabelPosition: Int
{
case top
case bottom
case bothSided
case topInside
case bottomInside
}
/// width of the x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers
@objc open var labelWidth = CGFloat(1.0)
/// height of the x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers
@objc open var labelHeight = CGFloat(1.0)
/// width of the (rotated) x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers
@objc open var labelRotatedWidth = CGFloat(1.0)
/// height of the (rotated) x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers
@objc open var labelRotatedHeight = CGFloat(1.0)
/// This is the angle for drawing the X axis labels (in degrees)
@objc open var labelRotationAngle = CGFloat(0.0)
/// if set to true, the chart will avoid that the first and last label entry in the chart "clip" off the edge of the chart
@objc open var avoidFirstLastClippingEnabled = false
/// the position of the x-labels relative to the chart
@objc open var labelPosition = LabelPosition.top
/// if set to true, word wrapping the labels will be enabled.
/// word wrapping is done using `(value width * labelRotatedWidth)`
///
/// - note: currently supports all charts except pie/radar/horizontal-bar*
@objc open var wordWrapEnabled = false
/// - returns: `true` if word wrapping the labels is enabled
@objc open var isWordWrapEnabled: Bool { return wordWrapEnabled }
/// the width for wrapping the labels, as percentage out of one value width.
/// used only when isWordWrapEnabled = true.
///
/// **default**: 1.0
@objc open var wordWrapWidthPercent: CGFloat = 1.0
public override init()
{
super.init()
self.yOffset = 4.0
}
@objc open var isAvoidFirstLastClippingEnabled: Bool
{
return avoidFirstLastClippingEnabled
}
}

View File

@@ -0,0 +1,179 @@
//
// YAxis.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
/// Class representing the y-axis labels settings and its entries.
/// Be aware that not all features the YLabels class provides are suitable for the RadarChart.
/// Customizations that affect the value range of the axis need to be applied before setting data for the chart.
@objc(ChartYAxis)
open class YAxis: AxisBase
{
@objc(YAxisLabelPosition)
public enum LabelPosition: Int
{
case outsideChart
case insideChart
}
/// Enum that specifies the axis a DataSet should be plotted against, either Left or Right.
@objc
public enum AxisDependency: Int
{
case left
case right
}
/// indicates if the bottom y-label entry is drawn or not
@objc open var drawBottomYLabelEntryEnabled = true
/// indicates if the top y-label entry is drawn or not
@objc open var drawTopYLabelEntryEnabled = true
/// flag that indicates if the axis is inverted or not
@objc open var inverted = false
/// flag that indicates if the zero-line should be drawn regardless of other grid lines
@objc open var drawZeroLineEnabled = false
/// Color of the zero line
@objc open var zeroLineColor: NSUIColor? = NSUIColor.gray
/// Width of the zero line
@objc open var zeroLineWidth: CGFloat = 1.0
/// This is how much (in pixels) into the dash pattern are we starting from.
@objc open var zeroLineDashPhase = CGFloat(0.0)
/// This is the actual dash pattern.
/// I.e. [2, 3] will paint [-- -- ]
/// [1, 3, 4, 2] will paint [- ---- - ---- ]
@objc open var zeroLineDashLengths: [CGFloat]?
/// axis space from the largest value to the top in percent of the total axis range
@objc open var spaceTop = CGFloat(0.1)
/// axis space from the smallest value to the bottom in percent of the total axis range
@objc open var spaceBottom = CGFloat(0.1)
/// the position of the y-labels relative to the chart
@objc open var labelPosition = LabelPosition.outsideChart
/// the side this axis object represents
private var _axisDependency = AxisDependency.left
/// the minimum width that the axis should take
///
/// **default**: 0.0
@objc open var minWidth = CGFloat(0)
/// the maximum width that the axis can take.
/// use Infinity for disabling the maximum.
///
/// **default**: CGFloat.infinity
@objc open var maxWidth = CGFloat(CGFloat.infinity)
public override init()
{
super.init()
self.yOffset = 0.0
}
@objc public init(position: AxisDependency)
{
super.init()
_axisDependency = position
self.yOffset = 0.0
}
@objc open var axisDependency: AxisDependency
{
return _axisDependency
}
@objc open func requiredSize() -> CGSize
{
let label = getLongestLabel() as NSString
var size = label.size(withAttributes: [NSAttributedStringKey.font: labelFont])
size.width += xOffset * 2.0
size.height += yOffset * 2.0
size.width = max(minWidth, min(size.width, maxWidth > 0.0 ? maxWidth : size.width))
return size
}
@objc open func getRequiredHeightSpace() -> CGFloat
{
return requiredSize().height
}
/// - returns: `true` if this axis needs horizontal offset, `false` ifno offset is needed.
@objc open var needsOffset: Bool
{
if isEnabled && isDrawLabelsEnabled && labelPosition == .outsideChart
{
return true
}
else
{
return false
}
}
@objc open var isInverted: Bool { return inverted }
open override func calculate(min dataMin: Double, max dataMax: Double)
{
// if custom, use value as is, else use data value
var min = _customAxisMin ? _axisMinimum : dataMin
var max = _customAxisMax ? _axisMaximum : dataMax
// temporary range (before calculations)
let range = abs(max - min)
// in case all values are equal
if range == 0.0
{
max = max + 1.0
min = min - 1.0
}
// bottom-space only effects non-custom min
if !_customAxisMin
{
let bottomSpace = range * Double(spaceBottom)
_axisMinimum = (min - bottomSpace)
}
// top-space only effects non-custom max
if !_customAxisMax
{
let topSpace = range * Double(spaceTop)
_axisMaximum = (max + topSpace)
}
// calc actual range
axisRange = abs(_axisMaximum - _axisMinimum)
}
@objc open var isDrawBottomYLabelEntryEnabled: Bool { return drawBottomYLabelEntryEnabled }
@objc open var isDrawTopYLabelEntryEnabled: Bool { return drawTopYLabelEntryEnabled }
}

View File

@@ -0,0 +1,424 @@
//
// BaseDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class ChartBaseDataSet: NSObject, IChartDataSet
{
public required override init()
{
super.init()
// default color
colors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0))
valueColors.append(NSUIColor.black)
}
@objc public init(label: String?)
{
super.init()
// default color
colors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0))
valueColors.append(NSUIColor.black)
self.label = label
}
// MARK: - Data functions and accessors
/// Use this method to tell the data set that the underlying data has changed
open func notifyDataSetChanged()
{
calcMinMax()
}
open func calcMinMax()
{
fatalError("calcMinMax is not implemented in ChartBaseDataSet")
}
open func calcMinMaxY(fromX: Double, toX: Double)
{
fatalError("calcMinMaxY(fromX:, toX:) is not implemented in ChartBaseDataSet")
}
open var yMin: Double
{
fatalError("yMin is not implemented in ChartBaseDataSet")
}
open var yMax: Double
{
fatalError("yMax is not implemented in ChartBaseDataSet")
}
open var xMin: Double
{
fatalError("xMin is not implemented in ChartBaseDataSet")
}
open var xMax: Double
{
fatalError("xMax is not implemented in ChartBaseDataSet")
}
open var entryCount: Int
{
fatalError("entryCount is not implemented in ChartBaseDataSet")
}
open func entryForIndex(_ i: Int) -> ChartDataEntry?
{
fatalError("entryForIndex is not implemented in ChartBaseDataSet")
}
open func entryForXValue(
_ x: Double,
closestToY y: Double,
rounding: ChartDataSetRounding) -> ChartDataEntry?
{
fatalError("entryForXValue(x, closestToY, rounding) is not implemented in ChartBaseDataSet")
}
open func entryForXValue(
_ x: Double,
closestToY y: Double) -> ChartDataEntry?
{
fatalError("entryForXValue(x, closestToY) is not implemented in ChartBaseDataSet")
}
open func entriesForXValue(_ x: Double) -> [ChartDataEntry]
{
fatalError("entriesForXValue is not implemented in ChartBaseDataSet")
}
open func entryIndex(
x xValue: Double,
closestToY y: Double,
rounding: ChartDataSetRounding) -> Int
{
fatalError("entryIndex(x, closestToY, rounding) is not implemented in ChartBaseDataSet")
}
open func entryIndex(entry e: ChartDataEntry) -> Int
{
fatalError("entryIndex(entry) is not implemented in ChartBaseDataSet")
}
open func addEntry(_ e: ChartDataEntry) -> Bool
{
fatalError("addEntry is not implemented in ChartBaseDataSet")
}
open func addEntryOrdered(_ e: ChartDataEntry) -> Bool
{
fatalError("addEntryOrdered is not implemented in ChartBaseDataSet")
}
@discardableResult open func removeEntry(_ entry: ChartDataEntry) -> Bool
{
fatalError("removeEntry is not implemented in ChartBaseDataSet")
}
@discardableResult open func removeEntry(index: Int) -> Bool
{
if let entry = entryForIndex(index)
{
return removeEntry(entry)
}
return false
}
@discardableResult open func removeEntry(x: Double) -> Bool
{
if let entry = entryForXValue(x, closestToY: Double.nan)
{
return removeEntry(entry)
}
return false
}
@discardableResult open func removeFirst() -> Bool
{
if entryCount > 0
{
if let entry = entryForIndex(0)
{
return removeEntry(entry)
}
}
return false
}
@discardableResult open func removeLast() -> Bool
{
if entryCount > 0
{
if let entry = entryForIndex(entryCount - 1)
{
return removeEntry(entry)
}
}
return false
}
open func contains(_ e: ChartDataEntry) -> Bool
{
fatalError("removeEntry is not implemented in ChartBaseDataSet")
}
open func clear()
{
fatalError("clear is not implemented in ChartBaseDataSet")
}
// MARK: - Styling functions and accessors
/// All the colors that are used for this DataSet.
/// Colors are reused as soon as the number of Entries the DataSet represents is higher than the size of the colors array.
open var colors = [NSUIColor]()
/// List representing all colors that are used for drawing the actual values for this DataSet
open var valueColors = [NSUIColor]()
/// The label string that describes the DataSet.
open var label: String? = "DataSet"
/// The axis this DataSet should be plotted against.
open var axisDependency = YAxis.AxisDependency.left
/// - returns: The color at the given index of the DataSet's color array.
/// This prevents out-of-bounds by performing a modulus on the color index, so colours will repeat themselves.
open func color(atIndex index: Int) -> NSUIColor
{
var index = index
if index < 0
{
index = 0
}
return colors[index % colors.count]
}
/// Resets all colors of this DataSet and recreates the colors array.
open func resetColors()
{
colors.removeAll(keepingCapacity: false)
}
/// Adds a new color to the colors array of the DataSet.
/// - parameter color: the color to add
open func addColor(_ color: NSUIColor)
{
colors.append(color)
}
/// Sets the one and **only** color that should be used for this DataSet.
/// Internally, this recreates the colors array and adds the specified color.
/// - parameter color: the color to set
open func setColor(_ color: NSUIColor)
{
colors.removeAll(keepingCapacity: false)
colors.append(color)
}
/// Sets colors to a single color a specific alpha value.
/// - parameter color: the color to set
/// - parameter alpha: alpha to apply to the set `color`
@objc open func setColor(_ color: NSUIColor, alpha: CGFloat)
{
setColor(color.withAlphaComponent(alpha))
}
/// Sets colors with a specific alpha value.
/// - parameter colors: the colors to set
/// - parameter alpha: alpha to apply to the set `colors`
@objc open func setColors(_ colors: [NSUIColor], alpha: CGFloat)
{
var colorsWithAlpha = colors
for i in 0 ..< colorsWithAlpha.count
{
colorsWithAlpha[i] = colorsWithAlpha[i] .withAlphaComponent(alpha)
}
self.colors = colorsWithAlpha
}
/// Sets colors with a specific alpha value.
/// - parameter colors: the colors to set
/// - parameter alpha: alpha to apply to the set `colors`
open func setColors(_ colors: NSUIColor...)
{
self.colors = colors
}
/// if true, value highlighting is enabled
open var highlightEnabled = true
/// - returns: `true` if value highlighting is enabled for this dataset
open var isHighlightEnabled: Bool { return highlightEnabled }
/// Custom formatter that is used instead of the auto-formatter if set
internal var _valueFormatter: IValueFormatter?
/// Custom formatter that is used instead of the auto-formatter if set
open var valueFormatter: IValueFormatter?
{
get
{
if needsFormatter
{
return ChartUtils.defaultValueFormatter()
}
return _valueFormatter
}
set
{
if newValue == nil { return }
_valueFormatter = newValue
}
}
open var needsFormatter: Bool
{
return _valueFormatter == nil
}
/// Sets/get a single color for value text.
/// Setting the color clears the colors array and adds a single color.
/// Getting will return the first color in the array.
open var valueTextColor: NSUIColor
{
get
{
return valueColors[0]
}
set
{
valueColors.removeAll(keepingCapacity: false)
valueColors.append(newValue)
}
}
/// - returns: The color at the specified index that is used for drawing the values inside the chart. Uses modulus internally.
open func valueTextColorAt(_ index: Int) -> NSUIColor
{
var index = index
if index < 0
{
index = 0
}
return valueColors[index % valueColors.count]
}
/// the font for the value-text labels
open var valueFont: NSUIFont = NSUIFont.systemFont(ofSize: 7.0)
/// The form to draw for this dataset in the legend.
open var form = Legend.Form.default
/// The form size to draw for this dataset in the legend.
///
/// Return `NaN` to use the default legend form size.
open var formSize: CGFloat = CGFloat.nan
/// The line width for drawing the form of this dataset in the legend
///
/// Return `NaN` to use the default legend form line width.
open var formLineWidth: CGFloat = CGFloat.nan
/// Line dash configuration for legend shapes that consist of lines.
///
/// This is how much (in pixels) into the dash pattern are we starting from.
open var formLineDashPhase: CGFloat = 0.0
/// Line dash configuration for legend shapes that consist of lines.
///
/// This is the actual dash pattern.
/// I.e. [2, 3] will paint [-- -- ]
/// [1, 3, 4, 2] will paint [- ---- - ---- ]
open var formLineDashLengths: [CGFloat]? = nil
/// Set this to true to draw y-values on the chart.
///
/// - note: For bar and line charts: if `maxVisibleCount` is reached, no values will be drawn even if this is enabled.
open var drawValuesEnabled = true
/// - returns: `true` if y-value drawing is enabled, `false` ifnot
open var isDrawValuesEnabled: Bool
{
return drawValuesEnabled
}
/// Set this to true to draw y-icons on the chart.
///
/// - note: For bar and line charts: if `maxVisibleCount` is reached, no icons will be drawn even if this is enabled.
open var drawIconsEnabled = true
/// Returns true if y-icon drawing is enabled, false if not
open var isDrawIconsEnabled: Bool
{
return drawIconsEnabled
}
/// Offset of icons drawn on the chart.
///
/// For all charts except Pie and Radar it will be ordinary (x offset, y offset).
///
/// For Pie and Radar chart it will be (y offset, distance from center offset); so if you want icon to be rendered under value, you should increase X component of CGPoint, and if you want icon to be rendered closet to center, you should decrease height component of CGPoint.
open var iconsOffset = CGPoint(x: 0, y: 0)
/// Set the visibility of this DataSet. If not visible, the DataSet will not be drawn to the chart upon refreshing it.
open var visible = true
/// - returns: `true` if this DataSet is visible inside the chart, or `false` ifit is currently hidden.
open var isVisible: Bool
{
return visible
}
// MARK: - NSObject
open override var description: String
{
return String(format: "%@, label: %@, %i entries", arguments: [NSStringFromClass(type(of: self)), self.label ?? "", self.entryCount])
}
open override var debugDescription: String
{
var desc = description + ":"
for i in 0 ..< self.entryCount
{
desc += "\n" + (self.entryForIndex(i)?.description ?? "")
}
return desc
}
// MARK: - NSCopying
@objc open func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = type(of: self).init()
copy.colors = colors
copy.valueColors = valueColors
copy.label = label
return copy
}
}

View File

@@ -0,0 +1,105 @@
//
// BarChartData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class BarChartData: BarLineScatterCandleBubbleChartData
{
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
/// The width of the bars on the x-axis, in values (not pixels)
///
/// **default**: 0.85
@objc open var barWidth = Double(0.85)
/// Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries.
/// Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified by the parameters.
/// Do not forget to call notifyDataSetChanged() on your BarChart object after calling this method.
///
/// - parameter the starting point on the x-axis where the grouping should begin
/// - parameter groupSpace: The space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f
/// - parameter barSpace: The space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f
@objc open func groupBars(fromX: Double, groupSpace: Double, barSpace: Double)
{
let setCount = _dataSets.count
if setCount <= 1
{
print("BarData needs to hold at least 2 BarDataSets to allow grouping.", terminator: "\n")
return
}
let max = maxEntryCountSet
let maxEntryCount = max?.entryCount ?? 0
let groupSpaceWidthHalf = groupSpace / 2.0
let barSpaceHalf = barSpace / 2.0
let barWidthHalf = self.barWidth / 2.0
var fromX = fromX
let interval = groupWidth(groupSpace: groupSpace, barSpace: barSpace)
for i in stride(from: 0, to: maxEntryCount, by: 1)
{
let start = fromX
fromX += groupSpaceWidthHalf
(_dataSets as? [IBarChartDataSet])?.forEach { set in
fromX += barSpaceHalf
fromX += barWidthHalf
if i < set.entryCount
{
if let entry = set.entryForIndex(i)
{
entry.x = fromX
}
}
fromX += barWidthHalf
fromX += barSpaceHalf
}
fromX += groupSpaceWidthHalf
let end = fromX
let innerInterval = end - start
let diff = interval - innerInterval
// correct rounding errors
if diff > 0 || diff < 0
{
fromX += diff
}
}
notifyDataChanged()
}
/// In case of grouped bars, this method returns the space an individual group of bar needs on the x-axis.
///
/// - parameter groupSpace:
/// - parameter barSpace:
@objc open func groupWidth(groupSpace: Double, barSpace: Double) -> Double
{
return Double(_dataSets.count) * (self.barWidth + barSpace) + groupSpace
}
}

View File

@@ -0,0 +1,247 @@
//
// BarChartDataEntry.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class BarChartDataEntry: ChartDataEntry
{
/// the values the stacked barchart holds
private var _yVals: [Double]?
/// the ranges for the individual stack values - automatically calculated
private var _ranges: [Range]?
/// the sum of all negative values this entry (if stacked) contains
private var _negativeSum: Double = 0.0
/// the sum of all positive values this entry (if stacked) contains
private var _positiveSum: Double = 0.0
public required init()
{
super.init()
}
/// Constructor for normal bars (not stacked).
public override init(x: Double, y: Double)
{
super.init(x: x, y: y)
}
/// Constructor for normal bars (not stacked).
public override init(x: Double, y: Double, data: AnyObject?)
{
super.init(x: x, y: y, data: data)
}
/// Constructor for normal bars (not stacked).
public override init(x: Double, y: Double, icon: NSUIImage?)
{
super.init(x: x, y: y, icon: icon)
}
/// Constructor for normal bars (not stacked).
public override init(x: Double, y: Double, icon: NSUIImage?, data: AnyObject?)
{
super.init(x: x, y: y, icon: icon, data: data)
}
/// Constructor for stacked bar entries.
@objc public init(x: Double, yValues: [Double])
{
super.init(x: x, y: BarChartDataEntry.calcSum(values: yValues))
self._yVals = yValues
calcPosNegSum()
calcRanges()
}
/// Constructor for stacked bar entries. One data object for whole stack
@objc public init(x: Double, yValues: [Double], data: AnyObject?)
{
super.init(x: x, y: BarChartDataEntry.calcSum(values: yValues), data: data)
self._yVals = yValues
calcPosNegSum()
calcRanges()
}
/// Constructor for stacked bar entries. One data object for whole stack
@objc public init(x: Double, yValues: [Double], icon: NSUIImage?, data: AnyObject?)
{
super.init(x: x, y: BarChartDataEntry.calcSum(values: yValues), icon: icon, data: data)
self._yVals = yValues
calcPosNegSum()
calcRanges()
}
/// Constructor for stacked bar entries. One data object for whole stack
@objc public init(x: Double, yValues: [Double], icon: NSUIImage?)
{
super.init(x: x, y: BarChartDataEntry.calcSum(values: yValues), icon: icon)
self._yVals = yValues
calcPosNegSum()
calcRanges()
}
@objc open func sumBelow(stackIndex :Int) -> Double
{
guard let yVals = _yVals else
{
return 0
}
var remainder: Double = 0.0
var index = yVals.count - 1
while (index > stackIndex && index >= 0)
{
remainder += yVals[index]
index -= 1
}
return remainder
}
/// - returns: The sum of all negative values this entry (if stacked) contains. (this is a positive number)
@objc open var negativeSum: Double
{
return _negativeSum
}
/// - returns: The sum of all positive values this entry (if stacked) contains.
@objc open var positiveSum: Double
{
return _positiveSum
}
@objc open func calcPosNegSum()
{
guard let _yVals = _yVals else
{
_positiveSum = 0.0
_negativeSum = 0.0
return
}
var sumNeg: Double = 0.0
var sumPos: Double = 0.0
for f in _yVals
{
if f < 0.0
{
sumNeg += -f
}
else
{
sumPos += f
}
}
_negativeSum = sumNeg
_positiveSum = sumPos
}
/// Splits up the stack-values of the given bar-entry into Range objects.
/// - parameter entry:
/// - returns:
@objc open func calcRanges()
{
let values = yValues
if values?.isEmpty != false
{
return
}
if _ranges == nil
{
_ranges = [Range]()
}
else
{
_ranges?.removeAll()
}
_ranges?.reserveCapacity(values!.count)
var negRemain = -negativeSum
var posRemain: Double = 0.0
for i in 0 ..< values!.count
{
let value = values![i]
if value < 0
{
_ranges?.append(Range(from: negRemain, to: negRemain - value))
negRemain -= value
}
else
{
_ranges?.append(Range(from: posRemain, to: posRemain + value))
posRemain += value
}
}
}
// MARK: Accessors
/// the values the stacked barchart holds
@objc open var isStacked: Bool { return _yVals != nil }
/// the values the stacked barchart holds
@objc open var yValues: [Double]?
{
get { return self._yVals }
set
{
self.y = BarChartDataEntry.calcSum(values: newValue)
self._yVals = newValue
calcPosNegSum()
calcRanges()
}
}
/// - returns: The ranges of the individual stack-entries. Will return null if this entry is not stacked.
@objc open var ranges: [Range]?
{
return _ranges
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! BarChartDataEntry
copy._yVals = _yVals
copy.y = y
copy._negativeSum = _negativeSum
return copy
}
/// Calculates the sum across all values of the given stack.
///
/// - parameter vals:
/// - returns:
private static func calcSum(values: [Double]?) -> Double
{
guard let values = values
else { return 0.0 }
var sum = 0.0
for f in values
{
sum += f
}
return sum
}
}

View File

@@ -0,0 +1,165 @@
//
// BarChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBarChartDataSet
{
private func initialize()
{
self.highlightColor = NSUIColor.black
self.calcStackSize(entries: values as! [BarChartDataEntry])
self.calcEntryCountIncludingStacks(entries: values as! [BarChartDataEntry])
}
public required init()
{
super.init()
initialize()
}
public override init(values: [ChartDataEntry]?, label: String?)
{
super.init(values: values, label: label)
initialize()
}
// MARK: - Data functions and accessors
/// the maximum number of bars that are stacked upon each other, this value
/// is calculated from the Entries that are added to the DataSet
private var _stackSize = 1
/// the overall entry count, including counting each stack-value individually
private var _entryCountStacks = 0
/// Calculates the total number of entries this DataSet represents, including
/// stacks. All values belonging to a stack are calculated separately.
private func calcEntryCountIncludingStacks(entries: [BarChartDataEntry])
{
_entryCountStacks = 0
for i in 0 ..< entries.count
{
if let vals = entries[i].yValues
{
_entryCountStacks += vals.count
}
else
{
_entryCountStacks += 1
}
}
}
/// calculates the maximum stacksize that occurs in the Entries array of this DataSet
private func calcStackSize(entries: [BarChartDataEntry])
{
for i in 0 ..< entries.count
{
if let vals = entries[i].yValues
{
if vals.count > _stackSize
{
_stackSize = vals.count
}
}
}
}
open override func calcMinMax(entry e: ChartDataEntry)
{
guard let e = e as? BarChartDataEntry
else { return }
if !e.y.isNaN
{
if e.yValues == nil
{
if e.y < _yMin
{
_yMin = e.y
}
if e.y > _yMax
{
_yMax = e.y
}
}
else
{
if -e.negativeSum < _yMin
{
_yMin = -e.negativeSum
}
if e.positiveSum > _yMax
{
_yMax = e.positiveSum
}
}
calcMinMaxX(entry: e)
}
}
/// - returns: The maximum number of bars that can be stacked upon another in this DataSet.
open var stackSize: Int
{
return _stackSize
}
/// - returns: `true` if this DataSet is stacked (stacksize > 1) or not.
open var isStacked: Bool
{
return _stackSize > 1 ? true : false
}
/// - returns: The overall entry count, including counting each stack-value individually
@objc open var entryCountStacks: Int
{
return _entryCountStacks
}
/// array of labels used to describe the different values of the stacked bars
open var stackLabels: [String] = ["Stack"]
// MARK: - Styling functions and accessors
/// the color used for drawing the bar-shadows. The bar shadows is a surface behind the bar that indicates the maximum value
open var barShadowColor = NSUIColor(red: 215.0/255.0, green: 215.0/255.0, blue: 215.0/255.0, alpha: 1.0)
/// the width used for drawing borders around the bars. If borderWidth == 0, no border will be drawn.
open var barBorderWidth : CGFloat = 0.0
/// the color drawing borders around the bars.
open var barBorderColor = NSUIColor.black
/// the alpha value (transparency) that is used for drawing the highlight indicator bar. min = 0.0 (fully transparent), max = 1.0 (fully opaque)
open var highlightAlpha = CGFloat(120.0 / 255.0)
// MARK: - NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! BarChartDataSet
copy._stackSize = _stackSize
copy._entryCountStacks = _entryCountStacks
copy.stackLabels = stackLabels
copy.barShadowColor = barShadowColor
copy.highlightAlpha = highlightAlpha
return copy
}
}

View File

@@ -0,0 +1,25 @@
//
// BarLineScatterCandleBubbleChartData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class BarLineScatterCandleBubbleChartData: ChartData
{
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
}

View File

@@ -0,0 +1,38 @@
//
// BarLineScatterCandleBubbleChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class BarLineScatterCandleBubbleChartDataSet: ChartDataSet, IBarLineScatterCandleBubbleChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
open var highlightColor = NSUIColor(red: 255.0/255.0, green: 187.0/255.0, blue: 115.0/255.0, alpha: 1.0)
open var highlightLineWidth = CGFloat(0.5)
open var highlightLineDashPhase = CGFloat(0.0)
open var highlightLineDashLengths: [CGFloat]?
// MARK: - NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! BarLineScatterCandleBubbleChartDataSet
copy.highlightColor = highlightColor
copy.highlightLineWidth = highlightLineWidth
copy.highlightLineDashPhase = highlightLineDashPhase
copy.highlightLineDashLengths = highlightLineDashLengths
return copy
}
}

View File

@@ -0,0 +1,32 @@
//
// BubbleChartData.swift
// Charts
//
// Bubble chart implementation:
// Copyright 2015 Pierre-Marc Airoldi
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class BubbleChartData: BarLineScatterCandleBubbleChartData
{
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
/// Sets the width of the circle that surrounds the bubble when highlighted for all DataSet objects this data object contains
@objc open func setHighlightCircleWidth(_ width: CGFloat)
{
(_dataSets as? [IBubbleChartDataSet])?.forEach { $0.highlightCircleWidth = width }
}
}

View File

@@ -0,0 +1,77 @@
//
// BubbleDataEntry.swift
// Charts
//
// Bubble chart implementation:
// Copyright 2015 Pierre-Marc Airoldi
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class BubbleChartDataEntry: ChartDataEntry
{
/// The size of the bubble.
@objc open var size = CGFloat(0.0)
public required init()
{
super.init()
}
/// - parameter x: The index on the x-axis.
/// - parameter y: The value on the y-axis.
/// - parameter size: The size of the bubble.
@objc public init(x: Double, y: Double, size: CGFloat)
{
super.init(x: x, y: y)
self.size = size
}
/// - parameter x: The index on the x-axis.
/// - parameter y: The value on the y-axis.
/// - parameter size: The size of the bubble.
/// - parameter data: Spot for additional data this Entry represents.
@objc public init(x: Double, y: Double, size: CGFloat, data: AnyObject?)
{
super.init(x: x, y: y, data: data)
self.size = size
}
/// - parameter x: The index on the x-axis.
/// - parameter y: The value on the y-axis.
/// - parameter size: The size of the bubble.
/// - parameter icon: icon image
@objc public init(x: Double, y: Double, size: CGFloat, icon: NSUIImage?)
{
super.init(x: x, y: y, icon: icon)
self.size = size
}
/// - parameter x: The index on the x-axis.
/// - parameter y: The value on the y-axis.
/// - parameter size: The size of the bubble.
/// - parameter icon: icon image
/// - parameter data: Spot for additional data this Entry represents.
@objc public init(x: Double, y: Double, size: CGFloat, icon: NSUIImage?, data: AnyObject?)
{
super.init(x: x, y: y, icon: icon, data: data)
self.size = size
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! BubbleChartDataEntry
copy.size = size
return copy
}
}

View File

@@ -0,0 +1,57 @@
//
// BubbleChartDataSet.swift
// Charts
//
// Bubble chart implementation:
// Copyright 2015 Pierre-Marc Airoldi
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class BubbleChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBubbleChartDataSet
{
// MARK: - Data functions and accessors
internal var _maxSize = CGFloat(0.0)
open var maxSize: CGFloat { return _maxSize }
@objc open var normalizeSizeEnabled: Bool = true
open var isNormalizeSizeEnabled: Bool { return normalizeSizeEnabled }
open override func calcMinMax(entry e: ChartDataEntry)
{
guard let e = e as? BubbleChartDataEntry
else { return }
super.calcMinMax(entry: e)
let size = e.size
if size > _maxSize
{
_maxSize = size
}
}
// MARK: - Styling functions and accessors
/// Sets/gets the width of the circle that surrounds the bubble when highlighted
open var highlightCircleWidth: CGFloat = 2.5
// MARK: - NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! BubbleChartDataSet
copy._xMin = _xMin
copy._xMax = _xMax
copy._maxSize = _maxSize
copy.highlightCircleWidth = highlightCircleWidth
return copy
}
}

View File

@@ -0,0 +1,25 @@
//
// CandleChartData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class CandleChartData: BarLineScatterCandleBubbleChartData
{
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
}

View File

@@ -0,0 +1,109 @@
//
// CandleChartDataEntry.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class CandleChartDataEntry: ChartDataEntry
{
/// shadow-high value
@objc open var high = Double(0.0)
/// shadow-low value
@objc open var low = Double(0.0)
/// close value
@objc open var close = Double(0.0)
/// open value
@objc open var open = Double(0.0)
public required init()
{
super.init()
}
@objc public init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double)
{
super.init(x: x, y: (shadowH + shadowL) / 2.0)
self.high = shadowH
self.low = shadowL
self.open = open
self.close = close
}
@objc public init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, data: AnyObject?)
{
super.init(x: x, y: (shadowH + shadowL) / 2.0, data: data)
self.high = shadowH
self.low = shadowL
self.open = open
self.close = close
}
@objc public init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, icon: NSUIImage?)
{
super.init(x: x, y: (shadowH + shadowL) / 2.0, icon: icon)
self.high = shadowH
self.low = shadowL
self.open = open
self.close = close
}
@objc public init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, icon: NSUIImage?, data: AnyObject?)
{
super.init(x: x, y: (shadowH + shadowL) / 2.0, icon: icon, data: data)
self.high = shadowH
self.low = shadowL
self.open = open
self.close = close
}
/// - returns: The overall range (difference) between shadow-high and shadow-low.
@objc open var shadowRange: Double
{
return abs(high - low)
}
/// - returns: The body size (difference between open and close).
@objc open var bodyRange: Double
{
return abs(open - close)
}
/// the center value of the candle. (Middle value between high and low)
open override var y: Double
{
get
{
return super.y
}
set
{
super.y = (high + low) / 2.0
}
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! CandleChartDataEntry
copy.high = high
copy.low = low
copy.open = open
copy.close = close
return copy
}
}

View File

@@ -0,0 +1,147 @@
//
// CandleChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class CandleChartDataSet: LineScatterCandleRadarChartDataSet, ICandleChartDataSet
{
public required init()
{
super.init()
}
public override init(values: [ChartDataEntry]?, label: String?)
{
super.init(values: values, label: label)
}
// MARK: - Data functions and accessors
open override func calcMinMax(entry e: ChartDataEntry)
{
guard let e = e as? CandleChartDataEntry
else { return }
if e.low < _yMin
{
_yMin = e.low
}
if e.high > _yMax
{
_yMax = e.high
}
calcMinMaxX(entry: e)
}
open override func calcMinMaxY(entry e: ChartDataEntry)
{
guard let e = e as? CandleChartDataEntry
else { return }
if e.high < _yMin
{
_yMin = e.high
}
if e.high > _yMax
{
_yMax = e.high
}
if e.low < _yMin
{
_yMin = e.low
}
if e.low > _yMax
{
_yMax = e.low
}
}
// MARK: - Styling functions and accessors
/// the space between the candle entries
///
/// **default**: 0.1 (10%)
private var _barSpace = CGFloat(0.1)
/// the space that is left out on the left and right side of each candle,
/// **default**: 0.1 (10%), max 0.45, min 0.0
open var barSpace: CGFloat
{
set
{
if newValue < 0.0
{
_barSpace = 0.0
}
else if newValue > 0.45
{
_barSpace = 0.45
}
else
{
_barSpace = newValue
}
}
get
{
return _barSpace
}
}
/// should the candle bars show?
/// when false, only "ticks" will show
///
/// **default**: true
open var showCandleBar: Bool = true
/// the width of the candle-shadow-line in pixels.
///
/// **default**: 1.5
open var shadowWidth = CGFloat(1.5)
/// the color of the shadow line
open var shadowColor: NSUIColor?
/// use candle color for the shadow
open var shadowColorSameAsCandle = false
/// Is the shadow color same as the candle color?
open var isShadowColorSameAsCandle: Bool { return shadowColorSameAsCandle }
/// color for open == close
open var neutralColor: NSUIColor?
/// color for open > close
open var increasingColor: NSUIColor?
/// color for open < close
open var decreasingColor: NSUIColor?
/// Are increasing values drawn as filled?
/// increasing candlesticks are traditionally hollow
open var increasingFilled = false
/// Are increasing values drawn as filled?
open var isIncreasingFilled: Bool { return increasingFilled }
/// Are decreasing values drawn as filled?
/// descreasing candlesticks are traditionally filled
open var decreasingFilled = true
/// Are decreasing values drawn as filled?
open var isDecreasingFilled: Bool { return decreasingFilled }
}

View File

@@ -0,0 +1,759 @@
//
// ChartData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class ChartData: NSObject
{
internal var _yMax: Double = -Double.greatestFiniteMagnitude
internal var _yMin: Double = Double.greatestFiniteMagnitude
internal var _xMax: Double = -Double.greatestFiniteMagnitude
internal var _xMin: Double = Double.greatestFiniteMagnitude
internal var _leftAxisMax: Double = -Double.greatestFiniteMagnitude
internal var _leftAxisMin: Double = Double.greatestFiniteMagnitude
internal var _rightAxisMax: Double = -Double.greatestFiniteMagnitude
internal var _rightAxisMin: Double = Double.greatestFiniteMagnitude
internal var _dataSets = [IChartDataSet]()
public override init()
{
super.init()
_dataSets = [IChartDataSet]()
}
@objc public init(dataSets: [IChartDataSet]?)
{
super.init()
_dataSets = dataSets ?? [IChartDataSet]()
self.initialize(dataSets: _dataSets)
}
@objc public convenience init(dataSet: IChartDataSet?)
{
self.init(dataSets: dataSet === nil ? nil : [dataSet!])
}
internal func initialize(dataSets: [IChartDataSet])
{
notifyDataChanged()
}
/// Call this method to let the ChartData know that the underlying data has changed.
/// Calling this performs all necessary recalculations needed when the contained data has changed.
@objc open func notifyDataChanged()
{
calcMinMax()
}
@objc open func calcMinMaxY(fromX: Double, toX: Double)
{
for set in _dataSets
{
set.calcMinMaxY(fromX: fromX, toX: toX)
}
// apply the new data
calcMinMax()
}
/// calc minimum and maximum y value over all datasets
@objc open func calcMinMax()
{
_yMax = -Double.greatestFiniteMagnitude
_yMin = Double.greatestFiniteMagnitude
_xMax = -Double.greatestFiniteMagnitude
_xMin = Double.greatestFiniteMagnitude
for set in _dataSets
{
calcMinMax(dataSet: set)
}
_leftAxisMax = -Double.greatestFiniteMagnitude
_leftAxisMin = Double.greatestFiniteMagnitude
_rightAxisMax = -Double.greatestFiniteMagnitude
_rightAxisMin = Double.greatestFiniteMagnitude
// left axis
let firstLeft = getFirstLeft(dataSets: dataSets)
if firstLeft !== nil
{
_leftAxisMax = firstLeft!.yMax
_leftAxisMin = firstLeft!.yMin
for dataSet in _dataSets
{
if dataSet.axisDependency == .left
{
if dataSet.yMin < _leftAxisMin
{
_leftAxisMin = dataSet.yMin
}
if dataSet.yMax > _leftAxisMax
{
_leftAxisMax = dataSet.yMax
}
}
}
}
// right axis
let firstRight = getFirstRight(dataSets: dataSets)
if firstRight !== nil
{
_rightAxisMax = firstRight!.yMax
_rightAxisMin = firstRight!.yMin
for dataSet in _dataSets
{
if dataSet.axisDependency == .right
{
if dataSet.yMin < _rightAxisMin
{
_rightAxisMin = dataSet.yMin
}
if dataSet.yMax > _rightAxisMax
{
_rightAxisMax = dataSet.yMax
}
}
}
}
}
/// Adjusts the current minimum and maximum values based on the provided Entry object.
@objc open func calcMinMax(entry e: ChartDataEntry, axis: YAxis.AxisDependency)
{
if _yMax < e.y
{
_yMax = e.y
}
if _yMin > e.y
{
_yMin = e.y
}
if _xMax < e.x
{
_xMax = e.x
}
if _xMin > e.x
{
_xMin = e.x
}
if axis == .left
{
if _leftAxisMax < e.y
{
_leftAxisMax = e.y
}
if _leftAxisMin > e.y
{
_leftAxisMin = e.y
}
}
else
{
if _rightAxisMax < e.y
{
_rightAxisMax = e.y
}
if _rightAxisMin > e.y
{
_rightAxisMin = e.y
}
}
}
/// Adjusts the minimum and maximum values based on the given DataSet.
@objc open func calcMinMax(dataSet d: IChartDataSet)
{
if _yMax < d.yMax
{
_yMax = d.yMax
}
if _yMin > d.yMin
{
_yMin = d.yMin
}
if _xMax < d.xMax
{
_xMax = d.xMax
}
if _xMin > d.xMin
{
_xMin = d.xMin
}
if d.axisDependency == .left
{
if _leftAxisMax < d.yMax
{
_leftAxisMax = d.yMax
}
if _leftAxisMin > d.yMin
{
_leftAxisMin = d.yMin
}
}
else
{
if _rightAxisMax < d.yMax
{
_rightAxisMax = d.yMax
}
if _rightAxisMin > d.yMin
{
_rightAxisMin = d.yMin
}
}
}
/// - returns: The number of LineDataSets this object contains
@objc open var dataSetCount: Int
{
return _dataSets.count
}
/// - returns: The smallest y-value the data object contains.
@objc open var yMin: Double
{
return _yMin
}
@nonobjc
open func getYMin() -> Double
{
return _yMin
}
@objc open func getYMin(axis: YAxis.AxisDependency) -> Double
{
if axis == .left
{
if _leftAxisMin == Double.greatestFiniteMagnitude
{
return _rightAxisMin
}
else
{
return _leftAxisMin
}
}
else
{
if _rightAxisMin == Double.greatestFiniteMagnitude
{
return _leftAxisMin
}
else
{
return _rightAxisMin
}
}
}
/// - returns: The greatest y-value the data object contains.
@objc open var yMax: Double
{
return _yMax
}
@nonobjc
open func getYMax() -> Double
{
return _yMax
}
@objc open func getYMax(axis: YAxis.AxisDependency) -> Double
{
if axis == .left
{
if _leftAxisMax == -Double.greatestFiniteMagnitude
{
return _rightAxisMax
}
else
{
return _leftAxisMax
}
}
else
{
if _rightAxisMax == -Double.greatestFiniteMagnitude
{
return _leftAxisMax
}
else
{
return _rightAxisMax
}
}
}
/// - returns: The minimum x-value the data object contains.
@objc open var xMin: Double
{
return _xMin
}
/// - returns: The maximum x-value the data object contains.
@objc open var xMax: Double
{
return _xMax
}
/// - returns: All DataSet objects this ChartData object holds.
@objc open var dataSets: [IChartDataSet]
{
get
{
return _dataSets
}
set
{
_dataSets = newValue
notifyDataChanged()
}
}
/// Retrieve the index of a ChartDataSet with a specific label from the ChartData. Search can be case sensitive or not.
///
/// **IMPORTANT: This method does calculations at runtime, do not over-use in performance critical situations.**
///
/// - parameter dataSets: the DataSet array to search
/// - parameter type:
/// - parameter ignorecase: if true, the search is not case-sensitive
/// - returns: The index of the DataSet Object with the given label. Sensitive or not.
internal func getDataSetIndexByLabel(_ label: String, ignorecase: Bool) -> Int
{
if ignorecase
{
for i in 0 ..< dataSets.count
{
if dataSets[i].label == nil
{
continue
}
if (label.caseInsensitiveCompare(dataSets[i].label!) == ComparisonResult.orderedSame)
{
return i
}
}
}
else
{
for i in 0 ..< dataSets.count
{
if label == dataSets[i].label
{
return i
}
}
}
return -1
}
/// - returns: The labels of all DataSets as a string array.
internal func dataSetLabels() -> [String]
{
var types = [String]()
for i in 0 ..< _dataSets.count
{
if dataSets[i].label == nil
{
continue
}
types[i] = _dataSets[i].label!
}
return types
}
/// Get the Entry for a corresponding highlight object
///
/// - parameter highlight:
/// - returns: The entry that is highlighted
@objc open func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry?
{
if highlight.dataSetIndex >= dataSets.count
{
return nil
}
else
{
return dataSets[highlight.dataSetIndex].entryForXValue(highlight.x, closestToY: highlight.y)
}
}
/// **IMPORTANT: This method does calculations at runtime. Use with care in performance critical situations.**
///
/// - parameter label:
/// - parameter ignorecase:
/// - returns: The DataSet Object with the given label. Sensitive or not.
@objc open func getDataSetByLabel(_ label: String, ignorecase: Bool) -> IChartDataSet?
{
let index = getDataSetIndexByLabel(label, ignorecase: ignorecase)
if index < 0 || index >= _dataSets.count
{
return nil
}
else
{
return _dataSets[index]
}
}
@objc open func getDataSetByIndex(_ index: Int) -> IChartDataSet!
{
if index < 0 || index >= _dataSets.count
{
return nil
}
return _dataSets[index]
}
@objc open func addDataSet(_ dataSet: IChartDataSet!)
{
calcMinMax(dataSet: dataSet)
_dataSets.append(dataSet)
}
/// Removes the given DataSet from this data object.
/// Also recalculates all minimum and maximum values.
///
/// - returns: `true` if a DataSet was removed, `false` ifno DataSet could be removed.
@objc @discardableResult open func removeDataSet(_ dataSet: IChartDataSet!) -> Bool
{
if dataSet === nil
{
return false
}
for i in 0 ..< _dataSets.count
{
if _dataSets[i] === dataSet
{
return removeDataSetByIndex(i)
}
}
return false
}
/// Removes the DataSet at the given index in the DataSet array from the data object.
/// Also recalculates all minimum and maximum values.
///
/// - returns: `true` if a DataSet was removed, `false` ifno DataSet could be removed.
@objc @discardableResult open func removeDataSetByIndex(_ index: Int) -> Bool
{
if index >= _dataSets.count || index < 0
{
return false
}
_dataSets.remove(at: index)
calcMinMax()
return true
}
/// Adds an Entry to the DataSet at the specified index. Entries are added to the end of the list.
@objc open func addEntry(_ e: ChartDataEntry, dataSetIndex: Int)
{
if _dataSets.count > dataSetIndex && dataSetIndex >= 0
{
let set = _dataSets[dataSetIndex]
if !set.addEntry(e) { return }
calcMinMax(entry: e, axis: set.axisDependency)
}
else
{
print("ChartData.addEntry() - Cannot add Entry because dataSetIndex too high or too low.", terminator: "\n")
}
}
/// Removes the given Entry object from the DataSet at the specified index.
@objc @discardableResult open func removeEntry(_ entry: ChartDataEntry, dataSetIndex: Int) -> Bool
{
// entry outofbounds
if dataSetIndex >= _dataSets.count
{
return false
}
// remove the entry from the dataset
let removed = _dataSets[dataSetIndex].removeEntry(entry)
if removed
{
calcMinMax()
}
return removed
}
/// Removes the Entry object closest to the given xIndex from the ChartDataSet at the
/// specified index.
/// - returns: `true` if an entry was removed, `false` ifno Entry was found that meets the specified requirements.
@objc @discardableResult open func removeEntry(xValue: Double, dataSetIndex: Int) -> Bool
{
if dataSetIndex >= _dataSets.count
{
return false
}
if let entry = _dataSets[dataSetIndex].entryForXValue(xValue, closestToY: Double.nan)
{
return removeEntry(entry, dataSetIndex: dataSetIndex)
}
return false
}
/// - returns: The DataSet that contains the provided Entry, or null, if no DataSet contains this entry.
@objc open func getDataSetForEntry(_ e: ChartDataEntry!) -> IChartDataSet?
{
if e == nil
{
return nil
}
for i in 0 ..< _dataSets.count
{
let set = _dataSets[i]
if e === set.entryForXValue(e.x, closestToY: e.y)
{
return set
}
}
return nil
}
/// - returns: The index of the provided DataSet in the DataSet array of this data object, or -1 if it does not exist.
@objc open func indexOfDataSet(_ dataSet: IChartDataSet) -> Int
{
for i in 0 ..< _dataSets.count
{
if _dataSets[i] === dataSet
{
return i
}
}
return -1
}
/// - returns: The first DataSet from the datasets-array that has it's dependency on the left axis. Returns null if no DataSet with left dependency could be found.
@objc open func getFirstLeft(dataSets: [IChartDataSet]) -> IChartDataSet?
{
for dataSet in dataSets
{
if dataSet.axisDependency == .left
{
return dataSet
}
}
return nil
}
/// - returns: The first DataSet from the datasets-array that has it's dependency on the right axis. Returns null if no DataSet with right dependency could be found.
@objc open func getFirstRight(dataSets: [IChartDataSet]) -> IChartDataSet?
{
for dataSet in _dataSets
{
if dataSet.axisDependency == .right
{
return dataSet
}
}
return nil
}
/// - returns: All colors used across all DataSet objects this object represents.
@objc open func getColors() -> [NSUIColor]?
{
var clrcnt = 0
for i in 0 ..< _dataSets.count
{
clrcnt += _dataSets[i].colors.count
}
var colors = [NSUIColor]()
for i in 0 ..< _dataSets.count
{
let clrs = _dataSets[i].colors
for clr in clrs
{
colors.append(clr)
}
}
return colors
}
/// Sets a custom IValueFormatter for all DataSets this data object contains.
@objc open func setValueFormatter(_ formatter: IValueFormatter?)
{
guard let formatter = formatter
else { return }
for set in dataSets
{
set.valueFormatter = formatter
}
}
/// Sets the color of the value-text (color in which the value-labels are drawn) for all DataSets this data object contains.
@objc open func setValueTextColor(_ color: NSUIColor!)
{
for set in dataSets
{
set.valueTextColor = color ?? set.valueTextColor
}
}
/// Sets the font for all value-labels for all DataSets this data object contains.
@objc open func setValueFont(_ font: NSUIFont!)
{
for set in dataSets
{
set.valueFont = font ?? set.valueFont
}
}
/// Enables / disables drawing values (value-text) for all DataSets this data object contains.
@objc open func setDrawValues(_ enabled: Bool)
{
for set in dataSets
{
set.drawValuesEnabled = enabled
}
}
/// Enables / disables highlighting values for all DataSets this data object contains.
/// If set to true, this means that values can be highlighted programmatically or by touch gesture.
@objc open var highlightEnabled: Bool
{
get
{
for set in dataSets
{
if !set.highlightEnabled
{
return false
}
}
return true
}
set
{
for set in dataSets
{
set.highlightEnabled = newValue
}
}
}
/// if true, value highlightning is enabled
@objc open var isHighlightEnabled: Bool { return highlightEnabled }
/// Clears this data object from all DataSets and removes all Entries.
/// Don't forget to invalidate the chart after this.
@objc open func clearValues()
{
dataSets.removeAll(keepingCapacity: false)
notifyDataChanged()
}
/// Checks if this data object contains the specified DataSet.
/// - returns: `true` if so, `false` ifnot.
@objc open func contains(dataSet: IChartDataSet) -> Bool
{
for set in dataSets
{
if set === dataSet
{
return true
}
}
return false
}
/// - returns: The total entry count across all DataSet objects this data object contains.
@objc open var entryCount: Int
{
var count = 0
for set in _dataSets
{
count += set.entryCount
}
return count
}
/// - returns: The DataSet object with the maximum number of entries or null if there are no DataSets.
@objc open var maxEntryCountSet: IChartDataSet?
{
if _dataSets.count == 0
{
return nil
}
var max = _dataSets[0]
for set in _dataSets
{
if set.entryCount > max.entryCount
{
max = set
}
}
return max
}
}

View File

@@ -0,0 +1,108 @@
//
// ChartDataEntry.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class ChartDataEntry: ChartDataEntryBase
{
/// the x value
@objc open var x = Double(0.0)
public required init()
{
super.init()
}
/// An Entry represents one single entry in the chart.
/// - parameter x: the x value
/// - parameter y: the y value (the actual value of the entry)
@objc public init(x: Double, y: Double)
{
super.init(y: y)
self.x = x
}
/// An Entry represents one single entry in the chart.
/// - parameter x: the x value
/// - parameter y: the y value (the actual value of the entry)
/// - parameter data: Space for additional data this Entry represents.
@objc public init(x: Double, y: Double, data: AnyObject?)
{
super.init(y: y)
self.x = x
self.data = data
}
/// An Entry represents one single entry in the chart.
/// - parameter x: the x value
/// - parameter y: the y value (the actual value of the entry)
/// - parameter icon: icon image
@objc public init(x: Double, y: Double, icon: NSUIImage?)
{
super.init(y: y, icon: icon)
self.x = x
}
/// An Entry represents one single entry in the chart.
/// - parameter x: the x value
/// - parameter y: the y value (the actual value of the entry)
/// - parameter icon: icon image
/// - parameter data: Space for additional data this Entry represents.
@objc public init(x: Double, y: Double, icon: NSUIImage?, data: AnyObject?)
{
super.init(y: y, icon: icon, data: data)
self.x = x
}
// MARK: NSObject
open override var description: String
{
return "ChartDataEntry, x: \(x), y \(y)"
}
// MARK: NSCopying
@objc open func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = type(of: self).init()
copy.x = x
copy.y = y
copy.data = data
return copy
}
}
// MARK: Equatable
extension ChartDataEntry/*: Equatable*/ {
open override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? ChartDataEntry else { return false }
if self === object
{
return true
}
return ((data == nil && object.data == nil) || (data?.isEqual(object.data) ?? false))
&& y == object.y
&& x == object.x
}
}

View File

@@ -0,0 +1,95 @@
//
// ChartDataEntryBase.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class ChartDataEntryBase: NSObject
{
/// the y value
@objc open var y = Double(0.0)
/// optional spot for additional data this Entry represents
@objc open var data: AnyObject?
/// optional icon image
@objc open var icon: NSUIImage?
public override required init()
{
super.init()
}
/// An Entry represents one single entry in the chart.
/// - parameter y: the y value (the actual value of the entry)
@objc public init(y: Double)
{
super.init()
self.y = y
}
/// - parameter y: the y value (the actual value of the entry)
/// - parameter data: Space for additional data this Entry represents.
@objc public init(y: Double, data: AnyObject?)
{
super.init()
self.y = y
self.data = data
}
/// - parameter y: the y value (the actual value of the entry)
/// - parameter icon: icon image
@objc public init(y: Double, icon: NSUIImage?)
{
super.init()
self.y = y
self.icon = icon
}
/// - parameter y: the y value (the actual value of the entry)
/// - parameter icon: icon image
/// - parameter data: Space for additional data this Entry represents.
@objc public init(y: Double, icon: NSUIImage?, data: AnyObject?)
{
super.init()
self.y = y
self.icon = icon
self.data = data
}
// MARK: NSObject
open override var description: String
{
return "ChartDataEntryBase, y \(y)"
}
}
// MARK: Equatable
extension ChartDataEntryBase/*: Equatable*/ {
open override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? ChartDataEntryBase else { return false }
if self === object
{
return true
}
return ((data == nil && object.data == nil) || (data?.isEqual(object.data) ?? false))
&& y == object.y
}
}

View File

@@ -0,0 +1,508 @@
//
// ChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
/// Determines how to round DataSet index values for `ChartDataSet.entryIndex(x, rounding)` when an exact x-value is not found.
@objc
public enum ChartDataSetRounding: Int
{
case up = 0
case down = 1
case closest = 2
}
/// The DataSet class represents one group or type of entries (Entry) in the Chart that belong together.
/// It is designed to logically separate different groups of values inside the Chart (e.g. the values for a specific line in the LineChart, or the values of a specific group of bars in the BarChart).
open class ChartDataSet: ChartBaseDataSet
{
public required init()
{
values = []
super.init()
}
public override init(label: String?)
{
values = []
super.init(label: label)
}
@objc public init(values: [ChartDataEntry]?, label: String?)
{
self.values = values ?? []
super.init(label: label)
self.calcMinMax()
}
@objc public convenience init(values: [ChartDataEntry]?)
{
self.init(values: values, label: "DataSet")
}
// MARK: - Data functions and accessors
/// *
/// - note: Calls `notifyDataSetChanged()` after setting a new value.
/// - returns: The array of y-values that this DataSet represents.
/// the entries that this dataset represents / holds together
@objc open var values: [ChartDataEntry]
{
didSet
{
if isIndirectValuesCall {
isIndirectValuesCall = false
return
}
notifyDataSetChanged()
}
}
// TODO: Temporary fix for performance. Will be removed in 4.0
private var isIndirectValuesCall = false
/// maximum y-value in the value array
internal var _yMax: Double = -Double.greatestFiniteMagnitude
/// minimum y-value in the value array
internal var _yMin: Double = Double.greatestFiniteMagnitude
/// maximum x-value in the value array
internal var _xMax: Double = -Double.greatestFiniteMagnitude
/// minimum x-value in the value array
internal var _xMin: Double = Double.greatestFiniteMagnitude
open override func calcMinMax()
{
_yMax = -Double.greatestFiniteMagnitude
_yMin = Double.greatestFiniteMagnitude
_xMax = -Double.greatestFiniteMagnitude
_xMin = Double.greatestFiniteMagnitude
guard !values.isEmpty else { return }
values.forEach { calcMinMax(entry: $0) }
}
open override func calcMinMaxY(fromX: Double, toX: Double)
{
_yMax = -Double.greatestFiniteMagnitude
_yMin = Double.greatestFiniteMagnitude
guard !values.isEmpty else { return }
let indexFrom = entryIndex(x: fromX, closestToY: Double.nan, rounding: .down)
let indexTo = entryIndex(x: toX, closestToY: Double.nan, rounding: .up)
guard !(indexTo < indexFrom) else { return }
(indexFrom...indexTo).forEach {
// only recalculate y
calcMinMaxY(entry: values[$0])
}
}
@objc open func calcMinMaxX(entry e: ChartDataEntry)
{
if e.x < _xMin
{
_xMin = e.x
}
if e.x > _xMax
{
_xMax = e.x
}
}
@objc open func calcMinMaxY(entry e: ChartDataEntry)
{
if e.y < _yMin
{
_yMin = e.y
}
if e.y > _yMax
{
_yMax = e.y
}
}
/// Updates the min and max x and y value of this DataSet based on the given Entry.
///
/// - parameter e:
internal func calcMinMax(entry e: ChartDataEntry)
{
calcMinMaxX(entry: e)
calcMinMaxY(entry: e)
}
/// - returns: The minimum y-value this DataSet holds
open override var yMin: Double { return _yMin }
/// - returns: The maximum y-value this DataSet holds
open override var yMax: Double { return _yMax }
/// - returns: The minimum x-value this DataSet holds
open override var xMin: Double { return _xMin }
/// - returns: The maximum x-value this DataSet holds
open override var xMax: Double { return _xMax }
/// - returns: The number of y-values this DataSet represents
open override var entryCount: Int { return values.count }
/// - returns: The entry object found at the given index (not x-value!)
/// - throws: out of bounds
/// if `i` is out of bounds, it may throw an out-of-bounds exception
open override func entryForIndex(_ i: Int) -> ChartDataEntry?
{
guard i >= values.startIndex, i < values.endIndex else {
return nil
}
return values[i]
}
/// - returns: The first Entry object found at the given x-value with binary search.
/// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value according to the rounding.
/// nil if no Entry object at that x-value.
/// - parameter xValue: the x-value
/// - parameter closestToY: If there are multiple y-values for the specified x-value,
/// - parameter rounding: determine whether to round up/down/closest if there is no Entry matching the provided x-value
open override func entryForXValue(
_ xValue: Double,
closestToY yValue: Double,
rounding: ChartDataSetRounding) -> ChartDataEntry?
{
let index = entryIndex(x: xValue, closestToY: yValue, rounding: rounding)
if index > -1
{
return values[index]
}
return nil
}
/// - returns: The first Entry object found at the given x-value with binary search.
/// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value.
/// nil if no Entry object at that x-value.
/// - parameter xValue: the x-value
/// - parameter closestToY: If there are multiple y-values for the specified x-value,
open override func entryForXValue(
_ xValue: Double,
closestToY yValue: Double) -> ChartDataEntry?
{
return entryForXValue(xValue, closestToY: yValue, rounding: .closest)
}
/// - returns: All Entry objects found at the given xIndex with binary search.
/// An empty array if no Entry object at that index.
open override func entriesForXValue(_ xValue: Double) -> [ChartDataEntry]
{
var entries = [ChartDataEntry]()
var low = values.startIndex
var high = values.endIndex - 1
while low <= high
{
var m = (high + low) / 2
var entry = values[m]
// if we have a match
if xValue == entry.x
{
while m > 0 && values[m - 1].x == xValue
{
m -= 1
}
high = values.endIndex
// loop over all "equal" entries
while m < high
{
entry = values[m]
if entry.x == xValue
{
entries.append(entry)
}
else
{
break
}
m += 1
}
break
}
else
{
if xValue > entry.x
{
low = m + 1
}
else
{
high = m - 1
}
}
}
return entries
}
/// - returns: The array-index of the specified entry.
/// If the no Entry at the specified x-value is found, this method returns the index of the Entry at the closest x-value according to the rounding.
///
/// - parameter xValue: x-value of the entry to search for
/// - parameter closestToY: If there are multiple y-values for the specified x-value,
/// - parameter rounding: Rounding method if exact value was not found
open override func entryIndex(
x xValue: Double,
closestToY yValue: Double,
rounding: ChartDataSetRounding) -> Int
{
var low = values.startIndex
var high = values.endIndex - 1
var closest = high
while low < high
{
let m = (low + high) / 2
let d1 = values[m].x - xValue
let d2 = values[m + 1].x - xValue
let ad1 = abs(d1), ad2 = abs(d2)
if ad2 < ad1
{
// [m + 1] is closer to xValue
// Search in an higher place
low = m + 1
}
else if ad1 < ad2
{
// [m] is closer to xValue
// Search in a lower place
high = m
}
else
{
// We have multiple sequential x-value with same distance
if d1 >= 0.0
{
// Search in a lower place
high = m
}
else if d1 < 0.0
{
// Search in an higher place
low = m + 1
}
}
closest = high
}
if closest != -1
{
let closestXValue = values[closest].x
if rounding == .up
{
// If rounding up, and found x-value is lower than specified x, and we can go upper...
if closestXValue < xValue && closest < values.endIndex - 1
{
closest += 1
}
}
else if rounding == .down
{
// If rounding down, and found x-value is upper than specified x, and we can go lower...
if closestXValue > xValue && closest > 0
{
closest -= 1
}
}
// Search by closest to y-value
if !yValue.isNaN
{
while closest > 0 && values[closest - 1].x == closestXValue
{
closest -= 1
}
var closestYValue = values[closest].y
var closestYIndex = closest
while true
{
closest += 1
if closest >= values.endIndex { break }
let value = values[closest]
if value.x != closestXValue { break }
if abs(value.y - yValue) < abs(closestYValue - yValue)
{
closestYValue = yValue
closestYIndex = closest
}
}
closest = closestYIndex
}
}
return closest
}
/// - returns: The array-index of the specified entry
///
/// - parameter e: the entry to search for
open override func entryIndex(entry e: ChartDataEntry) -> Int
{
for i in 0 ..< values.count
{
if values[i] === e
{
return i
}
}
return -1
}
/// Adds an Entry to the DataSet dynamically.
/// Entries are added to the end of the list.
/// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum.
/// - parameter e: the entry to add
/// - returns: True
open override func addEntry(_ e: ChartDataEntry) -> Bool
{
calcMinMax(entry: e)
isIndirectValuesCall = true
values.append(e)
return true
}
/// Adds an Entry to the DataSet dynamically.
/// Entries are added to their appropriate index respective to it's x-index.
/// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum.
/// - parameter e: the entry to add
/// - returns: True
open override func addEntryOrdered(_ e: ChartDataEntry) -> Bool
{
calcMinMax(entry: e)
isIndirectValuesCall = true
if values.count > 0 && values.last!.x > e.x
{
var closestIndex = entryIndex(x: e.x, closestToY: e.y, rounding: .up)
while values[closestIndex].x < e.x
{
closestIndex += 1
}
values.insert(e, at: closestIndex)
}
else
{
values.append(e)
}
return true
}
/// Removes an Entry from the DataSet dynamically.
/// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum.
/// - parameter entry: the entry to remove
/// - returns: `true` if the entry was removed successfully, else if the entry does not exist
open override func removeEntry(_ entry: ChartDataEntry) -> Bool
{
var removed = false
isIndirectValuesCall = true
for i in 0 ..< values.count
{
if values[i] === entry
{
values.remove(at: i)
removed = true
break
}
}
notifyDataSetChanged()
return removed
}
/// Removes the first Entry (at index 0) of this DataSet from the entries array.
///
/// - returns: `true` if successful, `false` if not.
open override func removeFirst() -> Bool
{
let entry: ChartDataEntry? = values.isEmpty ? nil : values.removeFirst()
return entry != nil
}
/// Removes the last Entry (at index size-1) of this DataSet from the entries array.
///
/// - returns: `true` if successful, `false` if not.
open override func removeLast() -> Bool
{
let entry: ChartDataEntry? = values.isEmpty ? nil : values.removeLast()
return entry != nil
}
/// Checks if this DataSet contains the specified Entry.
/// - returns: `true` if contains the entry, `false` if not.
open override func contains(_ e: ChartDataEntry) -> Bool
{
for entry in values
{
if entry == e
{
return true
}
}
return false
}
/// Removes all values from this DataSet and recalculates min and max value.
open override func clear()
{
values.removeAll(keepingCapacity: true)
}
// MARK: - Data functions and accessors
// MARK: - NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! ChartDataSet
copy.values = values
copy._yMax = _yMax
copy._yMin = _yMin
return copy
}
}

View File

@@ -0,0 +1,320 @@
//
// CombinedChartData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class CombinedChartData: BarLineScatterCandleBubbleChartData
{
private var _lineData: LineChartData!
private var _barData: BarChartData!
private var _scatterData: ScatterChartData!
private var _candleData: CandleChartData!
private var _bubbleData: BubbleChartData!
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
@objc open var lineData: LineChartData!
{
get
{
return _lineData
}
set
{
_lineData = newValue
notifyDataChanged()
}
}
@objc open var barData: BarChartData!
{
get
{
return _barData
}
set
{
_barData = newValue
notifyDataChanged()
}
}
@objc open var scatterData: ScatterChartData!
{
get
{
return _scatterData
}
set
{
_scatterData = newValue
notifyDataChanged()
}
}
@objc open var candleData: CandleChartData!
{
get
{
return _candleData
}
set
{
_candleData = newValue
notifyDataChanged()
}
}
@objc open var bubbleData: BubbleChartData!
{
get
{
return _bubbleData
}
set
{
_bubbleData = newValue
notifyDataChanged()
}
}
open override func calcMinMax()
{
_dataSets.removeAll()
_yMax = -Double.greatestFiniteMagnitude
_yMin = Double.greatestFiniteMagnitude
_xMax = -Double.greatestFiniteMagnitude
_xMin = Double.greatestFiniteMagnitude
_leftAxisMax = -Double.greatestFiniteMagnitude
_leftAxisMin = Double.greatestFiniteMagnitude
_rightAxisMax = -Double.greatestFiniteMagnitude
_rightAxisMin = Double.greatestFiniteMagnitude
let allData = self.allData
for data in allData
{
data.calcMinMax()
let sets = data.dataSets
_dataSets.append(contentsOf: sets)
if data.yMax > _yMax
{
_yMax = data.yMax
}
if data.yMin < _yMin
{
_yMin = data.yMin
}
if data.xMax > _xMax
{
_xMax = data.xMax
}
if data.xMin < _xMin
{
_xMin = data.xMin
}
for dataset in sets
{
if dataset.axisDependency == .left
{
if dataset.yMax > _leftAxisMax
{
_leftAxisMax = dataset.yMax
}
if dataset.yMin < _leftAxisMin
{
_leftAxisMin = dataset.yMin
}
}
else
{
if dataset.yMax > _rightAxisMax
{
_rightAxisMax = dataset.yMax
}
if dataset.yMin < _rightAxisMin
{
_rightAxisMin = dataset.yMin
}
}
}
}
}
/// - returns: All data objects in row: line-bar-scatter-candle-bubble if not null.
@objc open var allData: [ChartData]
{
var data = [ChartData]()
if lineData !== nil
{
data.append(lineData)
}
if barData !== nil
{
data.append(barData)
}
if scatterData !== nil
{
data.append(scatterData)
}
if candleData !== nil
{
data.append(candleData)
}
if bubbleData !== nil
{
data.append(bubbleData)
}
return data
}
@objc open func dataByIndex(_ index: Int) -> ChartData
{
return allData[index]
}
open func dataIndex(_ data: ChartData) -> Int?
{
return allData.index(of: data)
}
open override func removeDataSet(_ dataSet: IChartDataSet!) -> Bool
{
let datas = allData
var success = false
for data in datas
{
success = data.removeDataSet(dataSet)
if success
{
break
}
}
return success
}
open override func removeDataSetByIndex(_ index: Int) -> Bool
{
print("removeDataSet(index) not supported for CombinedData", terminator: "\n")
return false
}
open override func removeEntry(_ entry: ChartDataEntry, dataSetIndex: Int) -> Bool
{
print("removeEntry(entry, dataSetIndex) not supported for CombinedData", terminator: "\n")
return false
}
open override func removeEntry(xValue: Double, dataSetIndex: Int) -> Bool
{
print("removeEntry(xValue, dataSetIndex) not supported for CombinedData", terminator: "\n")
return false
}
open override func notifyDataChanged()
{
if _lineData !== nil
{
_lineData.notifyDataChanged()
}
if _barData !== nil
{
_barData.notifyDataChanged()
}
if _scatterData !== nil
{
_scatterData.notifyDataChanged()
}
if _candleData !== nil
{
_candleData.notifyDataChanged()
}
if _bubbleData !== nil
{
_bubbleData.notifyDataChanged()
}
super.notifyDataChanged() // recalculate everything
}
/// Get the Entry for a corresponding highlight object
///
/// - parameter highlight:
/// - returns: The entry that is highlighted
open override func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry?
{
if highlight.dataIndex >= allData.count
{
return nil
}
let data = dataByIndex(highlight.dataIndex)
if highlight.dataSetIndex >= data.dataSetCount
{
return nil
}
// The value of the highlighted entry could be NaN - if we are not interested in highlighting a specific value.
let entries = data.getDataSetByIndex(highlight.dataSetIndex).entriesForXValue(highlight.x)
for e in entries
{
if e.y == highlight.y || highlight.y.isNaN
{
return e
}
}
return nil
}
/// Get dataset for highlight
///
/// - Parameter highlight: current highlight
/// - Returns: dataset related to highlight
@objc open func getDataSetByHighlight(_ highlight: Highlight) -> IChartDataSet!
{
if highlight.dataIndex >= allData.count
{
return nil
}
let data = dataByIndex(highlight.dataIndex)
if highlight.dataSetIndex >= data.dataSetCount
{
return nil
}
return data.dataSets[highlight.dataSetIndex]
}
}

View File

@@ -0,0 +1,26 @@
//
// LineChartData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
/// Data object that encapsulates all data associated with a LineChart.
open class LineChartData: ChartData
{
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
}

View File

@@ -0,0 +1,178 @@
//
// LineChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class LineChartDataSet: LineRadarChartDataSet, ILineChartDataSet
{
@objc(LineChartMode)
public enum Mode: Int
{
case linear
case stepped
case cubicBezier
case horizontalBezier
}
private func initialize()
{
// default color
circleColors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0))
}
public required init()
{
super.init()
initialize()
}
public override init(values: [ChartDataEntry]?, label: String?)
{
super.init(values: values, label: label)
initialize()
}
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// The drawing mode for this line dataset
///
/// **default**: Linear
open var mode: Mode = Mode.linear
private var _cubicIntensity = CGFloat(0.2)
/// Intensity for cubic lines (min = 0.05, max = 1)
///
/// **default**: 0.2
open var cubicIntensity: CGFloat
{
get
{
return _cubicIntensity
}
set
{
_cubicIntensity = newValue
if _cubicIntensity > 1.0
{
_cubicIntensity = 1.0
}
if _cubicIntensity < 0.05
{
_cubicIntensity = 0.05
}
}
}
/// The radius of the drawn circles.
open var circleRadius = CGFloat(8.0)
/// The hole radius of the drawn circles
open var circleHoleRadius = CGFloat(4.0)
open var circleColors = [NSUIColor]()
/// - returns: The color at the given index of the DataSet's circle-color array.
/// Performs a IndexOutOfBounds check by modulus.
open func getCircleColor(atIndex index: Int) -> NSUIColor?
{
let size = circleColors.count
let index = index % size
if index >= size
{
return nil
}
return circleColors[index]
}
/// Sets the one and ONLY color that should be used for this DataSet.
/// Internally, this recreates the colors array and adds the specified color.
open func setCircleColor(_ color: NSUIColor)
{
circleColors.removeAll(keepingCapacity: false)
circleColors.append(color)
}
open func setCircleColors(_ colors: NSUIColor...)
{
circleColors.removeAll(keepingCapacity: false)
circleColors.append(contentsOf: colors)
}
/// Resets the circle-colors array and creates a new one
open func resetCircleColors(_ index: Int)
{
circleColors.removeAll(keepingCapacity: false)
}
/// If true, drawing circles is enabled
open var drawCirclesEnabled = true
/// - returns: `true` if drawing circles for this DataSet is enabled, `false` ifnot
open var isDrawCirclesEnabled: Bool { return drawCirclesEnabled }
/// The color of the inner circle (the circle-hole).
open var circleHoleColor: NSUIColor? = NSUIColor.white
/// `true` if drawing circles for this DataSet is enabled, `false` ifnot
open var drawCircleHoleEnabled = true
/// - returns: `true` if drawing the circle-holes is enabled, `false` ifnot.
open var isDrawCircleHoleEnabled: Bool { return drawCircleHoleEnabled }
/// This is how much (in pixels) into the dash pattern are we starting from.
open var lineDashPhase = CGFloat(0.0)
/// This is the actual dash pattern.
/// I.e. [2, 3] will paint [-- -- ]
/// [1, 3, 4, 2] will paint [- ---- - ---- ]
open var lineDashLengths: [CGFloat]?
/// Line cap type, default is CGLineCap.Butt
open var lineCapType = CGLineCap.butt
/// formatter for customizing the position of the fill-line
private var _fillFormatter: IFillFormatter = DefaultFillFormatter()
/// Sets a custom IFillFormatter to the chart that handles the position of the filled-line for each DataSet. Set this to null to use the default logic.
open var fillFormatter: IFillFormatter?
{
get
{
return _fillFormatter
}
set
{
_fillFormatter = newValue ?? DefaultFillFormatter()
}
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! LineChartDataSet
copy.circleColors = circleColors
copy.circleRadius = circleRadius
copy.cubicIntensity = cubicIntensity
copy.lineDashPhase = lineDashPhase
copy.lineDashLengths = lineDashLengths
copy.lineCapType = lineCapType
copy.drawCirclesEnabled = drawCirclesEnabled
copy.drawCircleHoleEnabled = drawCircleHoleEnabled
copy.mode = mode
return copy
}
}

View File

@@ -0,0 +1,94 @@
//
// LineRadarChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class LineRadarChartDataSet: LineScatterCandleRadarChartDataSet, ILineRadarChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// The color that is used for filling the line surface area.
private var _fillColor = NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)
/// The color that is used for filling the line surface area.
open var fillColor: NSUIColor
{
get { return _fillColor }
set
{
_fillColor = newValue
fill = nil
}
}
/// The object that is used for filling the area below the line.
/// **default**: nil
open var fill: Fill?
/// The alpha value that is used for filling the line surface,
/// **default**: 0.33
open var fillAlpha = CGFloat(0.33)
private var _lineWidth = CGFloat(1.0)
/// line width of the chart (min = 0.0, max = 10)
///
/// **default**: 1
open var lineWidth: CGFloat
{
get
{
return _lineWidth
}
set
{
if newValue < 0.0
{
_lineWidth = 0.0
}
else if newValue > 10.0
{
_lineWidth = 10.0
}
else
{
_lineWidth = newValue
}
}
}
/// Set to `true` if the DataSet should be drawn filled (surface), and not just as a line.
/// Disabling this will give great performance boost.
/// Please note that this method uses the path clipping for drawing the filled area (with images, gradients and layers).
open var drawFilledEnabled = false
/// - returns: `true` if filled drawing is enabled, `false` ifnot
open var isDrawFilledEnabled: Bool
{
return drawFilledEnabled
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! LineRadarChartDataSet
copy.fillColor = fillColor
copy._lineWidth = _lineWidth
copy.drawFilledEnabled = drawFilledEnabled
return copy
}
}

View File

@@ -0,0 +1,51 @@
//
// LineScatterCandleRadarChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class LineScatterCandleRadarChartDataSet: BarLineScatterCandleBubbleChartDataSet, ILineScatterCandleRadarChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// Enables / disables the horizontal highlight-indicator. If disabled, the indicator is not drawn.
open var drawHorizontalHighlightIndicatorEnabled = true
/// Enables / disables the vertical highlight-indicator. If disabled, the indicator is not drawn.
open var drawVerticalHighlightIndicatorEnabled = true
/// - returns: `true` if horizontal highlight indicator lines are enabled (drawn)
open var isHorizontalHighlightIndicatorEnabled: Bool { return drawHorizontalHighlightIndicatorEnabled }
/// - returns: `true` if vertical highlight indicator lines are enabled (drawn)
open var isVerticalHighlightIndicatorEnabled: Bool { return drawVerticalHighlightIndicatorEnabled }
/// Enables / disables both vertical and horizontal highlight-indicators.
/// :param: enabled
open func setDrawHighlightIndicators(_ enabled: Bool)
{
drawHorizontalHighlightIndicatorEnabled = enabled
drawVerticalHighlightIndicatorEnabled = enabled
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! LineScatterCandleRadarChartDataSet
copy.drawHorizontalHighlightIndicatorEnabled = drawHorizontalHighlightIndicatorEnabled
copy.drawVerticalHighlightIndicatorEnabled = drawVerticalHighlightIndicatorEnabled
return copy
}
}

View File

@@ -0,0 +1,130 @@
//
// PieData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
open class PieChartData: ChartData
{
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
/// - returns: All DataSet objects this ChartData object holds.
@objc open override var dataSets: [IChartDataSet]
{
get
{
assert(super.dataSets.count <= 1, "Found multiple data sets while pie chart only allows one")
return super.dataSets
}
set
{
super.dataSets = newValue
}
}
@objc var dataSet: IPieChartDataSet?
{
get
{
return dataSets.count > 0 ? dataSets[0] as? IPieChartDataSet : nil
}
set
{
if let newValue = newValue
{
dataSets = [newValue]
}
else
{
dataSets = []
}
}
}
open override func getDataSetByIndex(_ index: Int) -> IChartDataSet?
{
if index != 0
{
return nil
}
return super.getDataSetByIndex(index)
}
open override func getDataSetByLabel(_ label: String, ignorecase: Bool) -> IChartDataSet?
{
if dataSets.count == 0 || dataSets[0].label == nil
{
return nil
}
if ignorecase
{
if let label = dataSets[0].label, label.caseInsensitiveCompare(label) == .orderedSame
{
return dataSets[0]
}
}
else
{
if label == dataSets[0].label
{
return dataSets[0]
}
}
return nil
}
open override func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry?
{
return dataSet?.entryForIndex(Int(highlight.x))
}
open override func addDataSet(_ d: IChartDataSet!)
{
super.addDataSet(d)
}
/// Removes the DataSet at the given index in the DataSet array from the data object.
/// Also recalculates all minimum and maximum values.
///
/// - returns: `true` if a DataSet was removed, `false` ifno DataSet could be removed.
open override func removeDataSetByIndex(_ index: Int) -> Bool
{
if index >= _dataSets.count || index < 0
{
return false
}
return false
}
/// - returns: The total y-value sum across all DataSet objects the this object represents.
@objc open var yValueSum: Double
{
guard let dataSet = dataSet else { return 0.0 }
var yValueSum: Double = 0.0
for i in 0..<dataSet.entryCount
{
yValueSum += dataSet.entryForIndex(i)?.y ?? 0.0
}
return yValueSum
}
}

View File

@@ -0,0 +1,102 @@
//
// PieChartDataEntry.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class PieChartDataEntry: ChartDataEntry
{
public required init()
{
super.init()
}
/// - parameter value: The value on the y-axis
/// - parameter label: The label for the x-axis
@objc public convenience init(value: Double, label: String?)
{
self.init(value: value, label: label, icon: nil, data: nil)
}
/// - parameter value: The value on the y-axis
/// - parameter label: The label for the x-axis
/// - parameter data: Spot for additional data this Entry represents
@objc public convenience init(value: Double, label: String?, data: AnyObject?)
{
self.init(value: value, label: label, icon: nil, data: data)
}
/// - parameter value: The value on the y-axis
/// - parameter label: The label for the x-axis
/// - parameter icon: icon image
@objc public convenience init(value: Double, label: String?, icon: NSUIImage?)
{
self.init(value: value, label: label, icon: icon, data: nil)
}
/// - parameter value: The value on the y-axis
/// - parameter label: The label for the x-axis
/// - parameter icon: icon image
/// - parameter data: Spot for additional data this Entry represents
@objc public init(value: Double, label: String?, icon: NSUIImage?, data: AnyObject?)
{
super.init(x: 0.0, y: value, icon: icon, data: data)
self.label = label
}
/// - parameter value: The value on the y-axis
@objc public convenience init(value: Double)
{
self.init(value: value, label: nil, icon: nil, data: nil)
}
/// - parameter value: The value on the y-axis
/// - parameter data: Spot for additional data this Entry represents
@objc public convenience init(value: Double, data: AnyObject?)
{
self.init(value: value, label: nil, icon: nil, data: data)
}
/// - parameter value: The value on the y-axis
/// - parameter icon: icon image
@objc public convenience init(value: Double, icon: NSUIImage?)
{
self.init(value: value, label: nil, icon: icon, data: nil)
}
/// - parameter value: The value on the y-axis
/// - parameter icon: icon image
/// - parameter data: Spot for additional data this Entry represents
@objc public convenience init(value: Double, icon: NSUIImage?, data: AnyObject?)
{
self.init(value: value, label: nil, icon: icon, data: data)
}
// MARK: Data property accessors
@objc open var label: String?
@objc open var value: Double
{
get { return y }
set { y = newValue }
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! PieChartDataEntry
copy.label = label
return copy
}
}

View File

@@ -0,0 +1,121 @@
//
// PieChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class PieChartDataSet: ChartDataSet, IPieChartDataSet
{
@objc(PieChartValuePosition)
public enum ValuePosition: Int
{
case insideSlice
case outsideSlice
}
private func initialize()
{
self.valueTextColor = NSUIColor.white
self.valueFont = NSUIFont.systemFont(ofSize: 13.0)
}
public required init()
{
super.init()
initialize()
}
public override init(values: [ChartDataEntry]?, label: String?)
{
super.init(values: values, label: label)
initialize()
}
internal override func calcMinMax(entry e: ChartDataEntry)
{
calcMinMaxY(entry: e)
}
// MARK: - Styling functions and accessors
private var _sliceSpace = CGFloat(0.0)
/// the space in pixels between the pie-slices
/// **default**: 0
/// **maximum**: 20
open var sliceSpace: CGFloat
{
get
{
return _sliceSpace
}
set
{
var space = newValue
if space > 20.0
{
space = 20.0
}
if space < 0.0
{
space = 0.0
}
_sliceSpace = space
}
}
/// When enabled, slice spacing will be 0.0 when the smallest value is going to be smaller than the slice spacing itself.
open var automaticallyDisableSliceSpacing: Bool = false
/// indicates the selection distance of a pie slice
open var selectionShift = CGFloat(18.0)
open var xValuePosition: ValuePosition = .insideSlice
open var yValuePosition: ValuePosition = .insideSlice
/// When valuePosition is OutsideSlice, indicates line color
open var valueLineColor: NSUIColor? = NSUIColor.black
/// When valuePosition is OutsideSlice, indicates line width
open var valueLineWidth: CGFloat = 1.0
/// When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size
open var valueLinePart1OffsetPercentage: CGFloat = 0.75
/// When valuePosition is OutsideSlice, indicates length of first half of the line
open var valueLinePart1Length: CGFloat = 0.3
/// When valuePosition is OutsideSlice, indicates length of second half of the line
open var valueLinePart2Length: CGFloat = 0.4
/// When valuePosition is OutsideSlice, this allows variable line length
open var valueLineVariableLength: Bool = true
/// the font for the slice-text labels
open var entryLabelFont: NSUIFont? = nil
/// the color for the slice-text labels
open var entryLabelColor: NSUIColor? = nil
/// the color for the highlighted sector
open var highlightColor: NSUIColor? = nil
// MARK: - NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! PieChartDataSet
copy._sliceSpace = _sliceSpace
copy.selectionShift = selectionShift
copy.highlightColor = highlightColor
return copy
}
}

View File

@@ -0,0 +1,46 @@
//
// RadarChartData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class RadarChartData: ChartData
{
@objc open var highlightColor = NSUIColor(red: 255.0/255.0, green: 187.0/255.0, blue: 115.0/255.0, alpha: 1.0)
@objc open var highlightLineWidth = CGFloat(1.0)
@objc open var highlightLineDashPhase = CGFloat(0.0)
@objc open var highlightLineDashLengths: [CGFloat]?
/// Sets labels that should be drawn around the RadarChart at the end of each web line.
@objc open var labels = [String]()
/// Sets the labels that should be drawn around the RadarChart at the end of each web line.
open func setLabels(_ labels: String...)
{
self.labels = labels
}
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
open override func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry?
{
return getDataSetByIndex(highlight.dataSetIndex)?.entryForIndex(Int(highlight.x))
}
}

View File

@@ -0,0 +1,51 @@
//
// RadarChartDataEntry.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class RadarChartDataEntry: ChartDataEntry
{
public required init()
{
super.init()
}
/// - parameter value: The value on the y-axis.
/// - parameter data: Spot for additional data this Entry represents.
@objc public init(value: Double, data: AnyObject?)
{
super.init(x: 0.0, y: value, data: data)
}
/// - parameter value: The value on the y-axis.
@objc public convenience init(value: Double)
{
self.init(value: value, data: nil)
}
// MARK: Data property accessors
@objc open var value: Double
{
get { return y }
set { y = value }
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! RadarChartDataEntry
return copy
}
}

View File

@@ -0,0 +1,59 @@
//
// RadarChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class RadarChartDataSet: LineRadarChartDataSet, IRadarChartDataSet
{
private func initialize()
{
self.valueFont = NSUIFont.systemFont(ofSize: 13.0)
}
public required init()
{
super.init()
initialize()
}
public required override init(values: [ChartDataEntry]?, label: String?)
{
super.init(values: values, label: label)
initialize()
}
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// flag indicating whether highlight circle should be drawn or not
/// **default**: false
open var drawHighlightCircleEnabled: Bool = false
/// - returns: `true` if highlight circle should be drawn, `false` ifnot
open var isDrawHighlightCircleEnabled: Bool { return drawHighlightCircleEnabled }
open var highlightCircleFillColor: NSUIColor? = NSUIColor.white
/// The stroke color for highlight circle.
/// If `nil`, the color of the dataset is taken.
open var highlightCircleStrokeColor: NSUIColor?
open var highlightCircleStrokeAlpha: CGFloat = 0.3
open var highlightCircleInnerRadius: CGFloat = 3.0
open var highlightCircleOuterRadius: CGFloat = 4.0
open var highlightCircleStrokeWidth: CGFloat = 2.0
}

View File

@@ -0,0 +1,48 @@
//
// ScatterChartData.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class ScatterChartData: BarLineScatterCandleBubbleChartData
{
public override init()
{
super.init()
}
public override init(dataSets: [IChartDataSet]?)
{
super.init(dataSets: dataSets)
}
/// - returns: The maximum shape-size across all DataSets.
@objc open func getGreatestShapeSize() -> CGFloat
{
var max = CGFloat(0.0)
for set in _dataSets
{
let scatterDataSet = set as? IScatterChartDataSet
if scatterDataSet == nil
{
print("ScatterChartData: Found a DataSet which is not a ScatterChartDataSet", terminator: "\n")
}
else if let size = scatterDataSet?.scatterShapeSize, size > max
{
max = size
}
}
return max
}
}

View File

@@ -0,0 +1,78 @@
//
// ScatterChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class ScatterChartDataSet: LineScatterCandleRadarChartDataSet, IScatterChartDataSet
{
@objc(ScatterShape)
public enum Shape: Int
{
case square
case circle
case triangle
case cross
case x
case chevronUp
case chevronDown
}
/// The size the scatter shape will have
open var scatterShapeSize = CGFloat(10.0)
/// The radius of the hole in the shape (applies to Square, Circle and Triangle)
/// **default**: 0.0
open var scatterShapeHoleRadius: CGFloat = 0.0
/// Color for the hole in the shape. Setting to `nil` will behave as transparent.
/// **default**: nil
open var scatterShapeHoleColor: NSUIColor? = nil
/// Sets the ScatterShape this DataSet should be drawn with.
/// This will search for an available IShapeRenderer and set this renderer for the DataSet
@objc open func setScatterShape(_ shape: Shape)
{
self.shapeRenderer = ScatterChartDataSet.renderer(forShape: shape)
}
/// The IShapeRenderer responsible for rendering this DataSet.
/// This can also be used to set a custom IShapeRenderer aside from the default ones.
/// **default**: `SquareShapeRenderer`
open var shapeRenderer: IShapeRenderer? = SquareShapeRenderer()
@objc open class func renderer(forShape shape: Shape) -> IShapeRenderer
{
switch shape
{
case .square: return SquareShapeRenderer()
case .circle: return CircleShapeRenderer()
case .triangle: return TriangleShapeRenderer()
case .cross: return CrossShapeRenderer()
case .x: return XShapeRenderer()
case .chevronUp: return ChevronUpShapeRenderer()
case .chevronDown: return ChevronDownShapeRenderer()
}
}
// MARK: NSCopying
open override func copyWithZone(_ zone: NSZone?) -> AnyObject
{
let copy = super.copyWithZone(zone) as! ScatterChartDataSet
copy.scatterShapeSize = scatterShapeSize
copy.scatterShapeHoleRadius = scatterShapeHoleRadius
copy.scatterShapeHoleColor = scatterShapeHoleColor
copy.shapeRenderer = shapeRenderer
return copy
}
}

View File

@@ -0,0 +1,42 @@
//
// IBarChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol IBarChartDataSet: IBarLineScatterCandleBubbleChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// - returns: `true` if this DataSet is stacked (stacksize > 1) or not.
var isStacked: Bool { get }
/// - returns: The maximum number of bars that can be stacked upon another in this DataSet.
var stackSize: Int { get }
/// the color used for drawing the bar-shadows. The bar shadows is a surface behind the bar that indicates the maximum value
var barShadowColor: NSUIColor { get set }
/// the width used for drawing borders around the bars. If borderWidth == 0, no border will be drawn.
var barBorderWidth : CGFloat { get set }
/// the color drawing borders around the bars.
var barBorderColor: NSUIColor { get set }
/// the alpha value (transparency) that is used for drawing the highlight indicator bar. min = 0.0 (fully transparent), max = 1.0 (fully opaque)
var highlightAlpha: CGFloat { get set }
/// array of labels used to describe the different values of the stacked bars
var stackLabels: [String] { get set }
}

View File

@@ -0,0 +1,26 @@
//
// IBarLineScatterCandleBubbleChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol IBarLineScatterCandleBubbleChartDataSet: IChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
var highlightColor: NSUIColor { get set }
var highlightLineWidth: CGFloat { get set }
var highlightLineDashPhase: CGFloat { get set }
var highlightLineDashLengths: [CGFloat]? { get set }
}

View File

@@ -0,0 +1,27 @@
//
// IBubbleChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol IBubbleChartDataSet: IBarLineScatterCandleBubbleChartDataSet
{
// MARK: - Data functions and accessors
var maxSize: CGFloat { get }
var isNormalizeSizeEnabled: Bool { get }
// MARK: - Styling functions and accessors
/// Sets/gets the width of the circle that surrounds the bubble when highlighted
var highlightCircleWidth: CGFloat { get set }
}

View File

@@ -0,0 +1,66 @@
//
// ICandleChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol ICandleChartDataSet: ILineScatterCandleRadarChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// the space that is left out on the left and right side of each candle,
/// **default**: 0.1 (10%), max 0.45, min 0.0
var barSpace: CGFloat { get set }
/// should the candle bars show?
/// when false, only "ticks" will show
///
/// **default**: true
var showCandleBar: Bool { get set }
/// the width of the candle-shadow-line in pixels.
///
/// **default**: 3.0
var shadowWidth: CGFloat { get set }
/// the color of the shadow line
var shadowColor: NSUIColor? { get set }
/// use candle color for the shadow
var shadowColorSameAsCandle: Bool { get set }
/// Is the shadow color same as the candle color?
var isShadowColorSameAsCandle: Bool { get }
/// color for open == close
var neutralColor: NSUIColor? { get set }
/// color for open > close
var increasingColor: NSUIColor? { get set }
/// color for open < close
var decreasingColor: NSUIColor? { get set }
/// Are increasing values drawn as filled?
var increasingFilled: Bool { get set }
/// Are increasing values drawn as filled?
var isIncreasingFilled: Bool { get }
/// Are decreasing values drawn as filled?
var decreasingFilled: Bool { get set }
/// Are decreasing values drawn as filled?
var isDecreasingFilled: Bool { get }
}

View File

@@ -0,0 +1,261 @@
//
// IChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol IChartDataSet
{
// MARK: - Data functions and accessors
/// Use this method to tell the data set that the underlying data has changed
func notifyDataSetChanged()
/// Calculates the minimum and maximum x and y values (_xMin, _xMax, _yMin, _yMax).
func calcMinMax()
/// Calculates the min and max y-values from the Entry closest to the given fromX to the Entry closest to the given toX value.
/// This is only needed for the autoScaleMinMax feature.
func calcMinMaxY(fromX: Double, toX: Double)
/// - returns: The minimum y-value this DataSet holds
var yMin: Double { get }
/// - returns: The maximum y-value this DataSet holds
var yMax: Double { get }
/// - returns: The minimum x-value this DataSet holds
var xMin: Double { get }
/// - returns: The maximum x-value this DataSet holds
var xMax: Double { get }
/// - returns: The number of y-values this DataSet represents
var entryCount: Int { get }
/// - returns: The entry object found at the given index (not x-value!)
/// - throws: out of bounds
/// if `i` is out of bounds, it may throw an out-of-bounds exception
func entryForIndex(_ i: Int) -> ChartDataEntry?
/// - returns: The first Entry object found at the given x-value with binary search.
/// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value according to the rounding.
/// nil if no Entry object at that x-value.
/// - parameter xValue: the x-value
/// - parameter closestToY: If there are multiple y-values for the specified x-value,
/// - parameter rounding: determine whether to round up/down/closest if there is no Entry matching the provided x-value
func entryForXValue(
_ xValue: Double,
closestToY yValue: Double,
rounding: ChartDataSetRounding) -> ChartDataEntry?
/// - returns: The first Entry object found at the given x-value with binary search.
/// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value.
/// nil if no Entry object at that x-value.
/// - parameter xValue: the x-value
/// - parameter closestToY: If there are multiple y-values for the specified x-value,
func entryForXValue(
_ xValue: Double,
closestToY yValue: Double) -> ChartDataEntry?
/// - returns: All Entry objects found at the given x-value with binary search.
/// An empty array if no Entry object at that x-value.
func entriesForXValue(_ xValue: Double) -> [ChartDataEntry]
/// - returns: The array-index of the specified entry.
/// If the no Entry at the specified x-value is found, this method returns the index of the Entry at the closest x-value according to the rounding.
///
/// - parameter xValue: x-value of the entry to search for
/// - parameter closestToY: If there are multiple y-values for the specified x-value,
/// - parameter rounding: Rounding method if exact value was not found
func entryIndex(
x xValue: Double,
closestToY yValue: Double,
rounding: ChartDataSetRounding) -> Int
/// - returns: The array-index of the specified entry
///
/// - parameter e: the entry to search for
func entryIndex(entry e: ChartDataEntry) -> Int
/// Adds an Entry to the DataSet dynamically.
///
/// *optional feature, can return `false` ifnot implemented*
///
/// Entries are added to the end of the list.
/// - parameter e: the entry to add
/// - returns: `true` if the entry was added successfully, `false` ifthis feature is not supported
func addEntry(_ e: ChartDataEntry) -> Bool
/// Adds an Entry to the DataSet dynamically.
/// Entries are added to their appropriate index in the values array respective to their x-position.
/// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum.
///
/// *optional feature, can return `false` ifnot implemented*
///
/// Entries are added to the end of the list.
/// - parameter e: the entry to add
/// - returns: `true` if the entry was added successfully, `false` ifthis feature is not supported
func addEntryOrdered(_ e: ChartDataEntry) -> Bool
/// Removes an Entry from the DataSet dynamically.
///
/// *optional feature, can return `false` ifnot implemented*
///
/// - parameter entry: the entry to remove
/// - returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported
func removeEntry(_ entry: ChartDataEntry) -> Bool
/// Removes the Entry object at the given index in the values array from the DataSet.
///
/// *optional feature, can return `false` ifnot implemented*
///
/// - parameter index: the index of the entry to remove
/// - returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported
func removeEntry(index: Int) -> Bool
/// Removes the Entry object closest to the given x-value from the DataSet.
///
/// *optional feature, can return `false` ifnot implemented*
///
/// - parameter x: the x-value to remove
/// - returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported
func removeEntry(x: Double) -> Bool
/// Removes the first Entry (at index 0) of this DataSet from the entries array.
///
/// *optional feature, can return `false` ifnot implemented*
///
/// - returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported
func removeFirst() -> Bool
/// Removes the last Entry (at index 0) of this DataSet from the entries array.
///
/// *optional feature, can return `false` ifnot implemented*
///
/// - returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported
func removeLast() -> Bool
/// Checks if this DataSet contains the specified Entry.
///
/// - returns: `true` if contains the entry, `false` ifnot.
func contains(_ e: ChartDataEntry) -> Bool
/// Removes all values from this DataSet and does all necessary recalculations.
///
/// *optional feature, could throw if not implemented*
func clear()
// MARK: - Styling functions and accessors
/// The label string that describes the DataSet.
var label: String? { get }
/// The axis this DataSet should be plotted against.
var axisDependency: YAxis.AxisDependency { get }
/// List representing all colors that are used for drawing the actual values for this DataSet
var valueColors: [NSUIColor] { get }
/// All the colors that are used for this DataSet.
/// Colors are reused as soon as the number of Entries the DataSet represents is higher than the size of the colors array.
var colors: [NSUIColor] { get }
/// - returns: The color at the given index of the DataSet's color array.
/// This prevents out-of-bounds by performing a modulus on the color index, so colours will repeat themselves.
func color(atIndex: Int) -> NSUIColor
func resetColors()
func addColor(_ color: NSUIColor)
func setColor(_ color: NSUIColor)
/// if true, value highlighting is enabled
var highlightEnabled: Bool { get set }
/// - returns: `true` if value highlighting is enabled for this dataset
var isHighlightEnabled: Bool { get }
/// Custom formatter that is used instead of the auto-formatter if set
var valueFormatter: IValueFormatter? { get set }
/// - returns: `true` if the valueFormatter object of this DataSet is null.
var needsFormatter: Bool { get }
/// Sets/get a single color for value text.
/// Setting the color clears the colors array and adds a single color.
/// Getting will return the first color in the array.
var valueTextColor: NSUIColor { get set }
/// - returns: The color at the specified index that is used for drawing the values inside the chart. Uses modulus internally.
func valueTextColorAt(_ index: Int) -> NSUIColor
/// the font for the value-text labels
var valueFont: NSUIFont { get set }
/// The form to draw for this dataset in the legend.
///
/// Return `.Default` to use the default legend form.
var form: Legend.Form { get }
/// The form size to draw for this dataset in the legend.
///
/// Return `NaN` to use the default legend form size.
var formSize: CGFloat { get }
/// The line width for drawing the form of this dataset in the legend
///
/// Return `NaN` to use the default legend form line width.
var formLineWidth: CGFloat { get }
/// Line dash configuration for legend shapes that consist of lines.
///
/// This is how much (in pixels) into the dash pattern are we starting from.
var formLineDashPhase: CGFloat { get }
/// Line dash configuration for legend shapes that consist of lines.
///
/// This is the actual dash pattern.
/// I.e. [2, 3] will paint [-- -- ]
/// [1, 3, 4, 2] will paint [- ---- - ---- ]
var formLineDashLengths: [CGFloat]? { get }
/// Set this to true to draw y-values on the chart.
///
/// - note: For bar and line charts: if `maxVisibleCount` is reached, no values will be drawn even if this is enabled.
var drawValuesEnabled: Bool { get set }
/// - returns: `true` if y-value drawing is enabled, `false` ifnot
var isDrawValuesEnabled: Bool { get }
/// Set this to true to draw y-icons on the chart
///
/// - note: For bar and line charts: if `maxVisibleCount` is reached, no icons will be drawn even if this is enabled.
var drawIconsEnabled: Bool { get set }
/// Returns true if y-icon drawing is enabled, false if not
var isDrawIconsEnabled: Bool { get }
/// Offset of icons drawn on the chart.
///
/// For all charts except Pie and Radar it will be ordinary (x offset, y offset).
///
/// For Pie and Radar chart it will be (y offset, distance from center offset); so if you want icon to be rendered under value, you should increase X component of CGPoint, and if you want icon to be rendered closet to center, you should decrease height component of CGPoint.
var iconsOffset: CGPoint { get set }
/// Set the visibility of this DataSet. If not visible, the DataSet will not be drawn to the chart upon refreshing it.
var visible: Bool { get set }
/// - returns: `true` if this DataSet is visible inside the chart, or `false` ifit is currently hidden.
var isVisible: Bool { get }
}

View File

@@ -0,0 +1,80 @@
//
// ILineChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol ILineChartDataSet: ILineRadarChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// The drawing mode for this line dataset
///
/// **default**: Linear
var mode: LineChartDataSet.Mode { get set }
/// Intensity for cubic lines (min = 0.05, max = 1)
///
/// **default**: 0.2
var cubicIntensity: CGFloat { get set }
/// The radius of the drawn circles.
var circleRadius: CGFloat { get set }
/// The hole radius of the drawn circles.
var circleHoleRadius: CGFloat { get set }
var circleColors: [NSUIColor] { get set }
/// - returns: The color at the given index of the DataSet's circle-color array.
/// Performs a IndexOutOfBounds check by modulus.
func getCircleColor(atIndex: Int) -> NSUIColor?
/// Sets the one and ONLY color that should be used for this DataSet.
/// Internally, this recreates the colors array and adds the specified color.
func setCircleColor(_ color: NSUIColor)
/// Resets the circle-colors array and creates a new one
func resetCircleColors(_ index: Int)
/// If true, drawing circles is enabled
var drawCirclesEnabled: Bool { get set }
/// - returns: `true` if drawing circles for this DataSet is enabled, `false` ifnot
var isDrawCirclesEnabled: Bool { get }
/// The color of the inner circle (the circle-hole).
var circleHoleColor: NSUIColor? { get set }
/// `true` if drawing circles for this DataSet is enabled, `false` ifnot
var drawCircleHoleEnabled: Bool { get set }
/// - returns: `true` if drawing the circle-holes is enabled, `false` ifnot.
var isDrawCircleHoleEnabled: Bool { get }
/// This is how much (in pixels) into the dash pattern are we starting from.
var lineDashPhase: CGFloat { get }
/// This is the actual dash pattern.
/// I.e. [2, 3] will paint [-- -- ]
/// [1, 3, 4, 2] will paint [- ---- - ---- ]
var lineDashLengths: [CGFloat]? { get set }
/// Line cap type, default is CGLineCap.Butt
var lineCapType: CGLineCap { get set }
/// Sets a custom IFillFormatter to the chart that handles the position of the filled-line for each DataSet. Set this to null to use the default logic.
var fillFormatter: IFillFormatter? { get set }
}

View File

@@ -0,0 +1,45 @@
//
// ILineRadarChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol ILineRadarChartDataSet: ILineScatterCandleRadarChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// The color that is used for filling the line surface area.
var fillColor: NSUIColor { get set }
/// - returns: The object that is used for filling the area below the line.
/// **default**: nil
var fill: Fill? { get set }
/// The alpha value that is used for filling the line surface.
/// **default**: 0.33
var fillAlpha: CGFloat { get set }
/// line width of the chart (min = 0.0, max = 10)
///
/// **default**: 1
var lineWidth: CGFloat { get set }
/// Set to `true` if the DataSet should be drawn filled (surface), and not just as a line.
/// Disabling this will give great performance boost.
/// Please note that this method uses the path clipping for drawing the filled area (with images, gradients and layers).
var drawFilledEnabled: Bool { get set }
/// - returns: `true` if filled drawing is enabled, `false` if not
var isDrawFilledEnabled: Bool { get }
}

View File

@@ -0,0 +1,36 @@
//
// ILineScatterCandleRadarChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
@objc
public protocol ILineScatterCandleRadarChartDataSet: IBarLineScatterCandleBubbleChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// Enables / disables the horizontal highlight-indicator. If disabled, the indicator is not drawn.
var drawHorizontalHighlightIndicatorEnabled: Bool { get set }
/// Enables / disables the vertical highlight-indicator. If disabled, the indicator is not drawn.
var drawVerticalHighlightIndicatorEnabled: Bool { get set }
/// - returns: `true` if horizontal highlight indicator lines are enabled (drawn)
var isHorizontalHighlightIndicatorEnabled: Bool { get }
/// - returns: `true` if vertical highlight indicator lines are enabled (drawn)
var isVerticalHighlightIndicatorEnabled: Bool { get }
/// Enables / disables both vertical and horizontal highlight-indicators.
/// :param: enabled
func setDrawHighlightIndicators(_ enabled: Bool)
}

View File

@@ -0,0 +1,64 @@
//
// IPieChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc
public protocol IPieChartDataSet: IChartDataSet
{
// MARK: - Styling functions and accessors
/// the space in pixels between the pie-slices
/// **default**: 0
/// **maximum**: 20
var sliceSpace: CGFloat { get set }
/// When enabled, slice spacing will be 0.0 when the smallest value is going to be smaller than the slice spacing itself.
var automaticallyDisableSliceSpacing: Bool { get set }
/// indicates the selection distance of a pie slice
var selectionShift: CGFloat { get set }
var xValuePosition: PieChartDataSet.ValuePosition { get set }
var yValuePosition: PieChartDataSet.ValuePosition { get set }
/// When valuePosition is OutsideSlice, indicates line color
var valueLineColor: NSUIColor? { get set }
/// When valuePosition is OutsideSlice, indicates line width
var valueLineWidth: CGFloat { get set }
/// When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size
var valueLinePart1OffsetPercentage: CGFloat { get set }
/// When valuePosition is OutsideSlice, indicates length of first half of the line
var valueLinePart1Length: CGFloat { get set }
/// When valuePosition is OutsideSlice, indicates length of second half of the line
var valueLinePart2Length: CGFloat { get set }
/// When valuePosition is OutsideSlice, this allows variable line length
var valueLineVariableLength: Bool { get set }
/// the font for the slice-text labels
var entryLabelFont: NSUIFont? { get set }
/// the color for the slice-text labels
var entryLabelColor: NSUIColor? { get set }
/// get/sets the color for the highlighted sector
var highlightColor: NSUIColor? { get set }
}

View File

@@ -0,0 +1,40 @@
//
// IRadarChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol IRadarChartDataSet: ILineRadarChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// flag indicating whether highlight circle should be drawn or not
var drawHighlightCircleEnabled: Bool { get set }
var isDrawHighlightCircleEnabled: Bool { get }
var highlightCircleFillColor: NSUIColor? { get set }
/// The stroke color for highlight circle.
/// If `nil`, the color of the dataset is taken.
var highlightCircleStrokeColor: NSUIColor? { get set }
var highlightCircleStrokeAlpha: CGFloat { get set }
var highlightCircleInnerRadius: CGFloat { get set }
var highlightCircleOuterRadius: CGFloat { get set }
var highlightCircleStrokeWidth: CGFloat { get set }
}

View File

@@ -0,0 +1,36 @@
//
// IScatterChartDataSet.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol IScatterChartDataSet: ILineScatterCandleRadarChartDataSet
{
// MARK: - Data functions and accessors
// MARK: - Styling functions and accessors
/// - returns: The size the scatter shape will have
var scatterShapeSize: CGFloat { get }
/// - returns: The radius of the hole in the shape (applies to Square, Circle and Triangle)
/// Set this to <= 0 to remove holes.
/// **default**: 0.0
var scatterShapeHoleRadius: CGFloat { get }
/// - returns: Color for the hole in the shape. Setting to `nil` will behave as transparent.
/// **default**: nil
var scatterShapeHoleColor: NSUIColor? { get }
/// - returns: The IShapeRenderer responsible for rendering this DataSet.
var shapeRenderer: IShapeRenderer? { get }
}

View File

@@ -0,0 +1,152 @@
//
// DataApproximator+N.swift
// Charts
//
// Created by M Ivaniushchenko on 9/6/17.
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
extension CGPoint {
fileprivate func distanceToLine(from linePoint1: CGPoint, to linePoint2: CGPoint) -> CGFloat {
let dx = linePoint2.x - linePoint1.x
let dy = linePoint2.y - linePoint1.y
let dividend = fabs(dy * self.x - dx * self.y - linePoint1.x * linePoint2.y + linePoint2.x * linePoint1.y)
let divisor = sqrt(dx * dx + dy * dy)
return dividend / divisor
}
}
private struct LineAlt {
let start: Int
let end: Int
var distance: CGFloat = 0
var index: Int = 0
init(start: Int, end: Int, points: [CGPoint]) {
self.start = start
self.end = end
let startPoint = points[start]
let endPoint = points[end]
guard (end > start + 1) else {
return
}
for i in start + 1 ..< end {
let currentPoint = points[i]
let distance = currentPoint.distanceToLine(from: startPoint, to: endPoint)
if distance > self.distance {
self.index = i
self.distance = distance
}
}
}
}
extension LineAlt: Comparable {
static func ==(lhs: LineAlt, rhs: LineAlt) -> Bool {
return (lhs.start == rhs.start) && (lhs.end == rhs.end) && (lhs.index == rhs.index)
}
static func <(lhs: LineAlt, rhs: LineAlt) -> Bool {
return lhs.distance < rhs.distance
}
}
extension DataApproximator {
/// uses the douglas peuker algorithm to reduce the given arraylist of entries to given number of points
/// More algorithm details here - http://psimpl.sourceforge.net/douglas-peucker.html
@objc open class func reduceWithDouglasPeukerN(_ points: [CGPoint], resultCount: Int) -> [CGPoint]
{
// if a shape has 2 or less points it cannot be reduced
if resultCount <= 2 || resultCount >= points.count
{
return points
}
var keep = [Bool](repeating: false, count: points.count)
// first and last always stay
keep[0] = true
keep[points.count - 1] = true
var currentStoredPoints = 2
var queue = [LineAlt]()
let line = LineAlt(start: 0, end: points.count - 1, points: points)
queue.append(line)
repeat {
let line = queue.popLast()!
// store the key
keep[line.index] = true
// check point count tolerance
currentStoredPoints += 1
if (currentStoredPoints == resultCount) {
break;
}
// split the polyline at the key and recurse
let left = LineAlt(start: line.start, end: line.index, points: points)
if (left.index > 0) {
self.insertLine(left, into: &queue)
}
let right = LineAlt(start: line.index, end: line.end, points: points)
if (right.index > 0) {
self.insertLine(right, into: &queue)
}
} while !queue.isEmpty
// create a new array with series, only take the kept ones
let reducedEntries = points.enumerated().compactMap { (index: Int, point: CGPoint) -> CGPoint? in
return keep[index] ? point : nil
}
return reducedEntries
}
// Keeps array sorted
private static func insertLine(_ line: LineAlt, into array: inout [LineAlt]) {
let insertionIndex = self.insertionIndex(for: line, into: &array)
array.insert(line, at: insertionIndex)
}
private static func insertionIndex(for line: LineAlt, into array: inout [LineAlt]) -> Int {
var indices = array.indices
while !indices.isEmpty {
let midIndex = indices.lowerBound.advanced(by: indices.count / 2)
let midLine = array[midIndex]
if midLine == line {
return midIndex
}
else if (line < midLine) {
// perform search in left half
indices = indices.lowerBound..<midIndex
}
else {
// perform search in right half
indices = (midIndex + 1)..<indices.upperBound
}
}
return indices.lowerBound
}
}

View File

@@ -0,0 +1,123 @@
//
// DataApproximator.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(ChartDataApproximator)
open class DataApproximator: NSObject
{
/// uses the douglas peuker algorithm to reduce the given arraylist of entries
@objc open class func reduceWithDouglasPeuker(_ points: [CGPoint], tolerance: CGFloat) -> [CGPoint]
{
// if a shape has 2 or less points it cannot be reduced
if tolerance <= 0 || points.count < 3
{
return points
}
var keep = [Bool](repeating: false, count: points.count)
// first and last always stay
keep[0] = true
keep[points.count - 1] = true
// first and last entry are entry point to recursion
reduceWithDouglasPeuker(points: points,
tolerance: tolerance,
start: 0,
end: points.count - 1,
keep: &keep)
// create a new array with series, only take the kept ones
var reducedEntries = [CGPoint]()
for i in 0 ..< points.count
{
if keep[i]
{
reducedEntries.append(points[i])
}
}
return reducedEntries
}
/// apply the Douglas-Peucker-Reduction to an array of `CGPoint`s with a given tolerance
///
/// - parameter points:
/// - parameter tolerance:
/// - parameter start:
/// - parameter end:
open class func reduceWithDouglasPeuker(
points: [CGPoint],
tolerance: CGFloat,
start: Int,
end: Int,
keep: inout [Bool])
{
if end <= start + 1
{
// recursion finished
return
}
var greatestIndex = Int(0)
var greatestDistance = CGFloat(0.0)
let line = Line(pt1: points[start], pt2: points[end])
for i in start + 1 ..< end
{
let distance = line.distance(toPoint: points[i])
if distance > greatestDistance
{
greatestDistance = distance
greatestIndex = i
}
}
if greatestDistance > tolerance
{
// keep max dist point
keep[greatestIndex] = true
// recursive call
reduceWithDouglasPeuker(points: points, tolerance: tolerance, start: start, end: greatestIndex, keep: &keep)
reduceWithDouglasPeuker(points: points, tolerance: tolerance, start: greatestIndex, end: end, keep: &keep)
} // else don't keep the point...
}
private class Line
{
var sxey: CGFloat
var exsy: CGFloat
var dx: CGFloat
var dy: CGFloat
var length: CGFloat
init(pt1: CGPoint, pt2: CGPoint)
{
dx = pt1.x - pt2.x
dy = pt1.y - pt2.y
sxey = pt1.x * pt2.y
exsy = pt2.x * pt1.y
length = sqrt(dx * dx + dy * dy)
}
func distance(toPoint pt: CGPoint) -> CGFloat
{
return abs(dy * pt.x - dx * pt.y + sxey - exsy) / length
}
}
}

View File

@@ -0,0 +1,100 @@
//
// DefaultAxisValueFormatter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
@objc(ChartDefaultAxisValueFormatter)
open class DefaultAxisValueFormatter: NSObject, IAxisValueFormatter
{
public typealias Block = (
_ value: Double,
_ axis: AxisBase?) -> String
@objc open var block: Block?
@objc open var hasAutoDecimals: Bool = false
private var _formatter: NumberFormatter?
@objc open var formatter: NumberFormatter?
{
get { return _formatter }
set
{
hasAutoDecimals = false
_formatter = newValue
}
}
// TODO: Documentation. Especially the nil case
private var _decimals: Int?
open var decimals: Int?
{
get { return _decimals }
set
{
_decimals = newValue
if let digits = newValue
{
self.formatter?.minimumFractionDigits = digits
self.formatter?.maximumFractionDigits = digits
self.formatter?.usesGroupingSeparator = true
}
}
}
public override init()
{
super.init()
self.formatter = NumberFormatter()
hasAutoDecimals = true
}
@objc public init(formatter: NumberFormatter)
{
super.init()
self.formatter = formatter
}
@objc public init(decimals: Int)
{
super.init()
self.formatter = NumberFormatter()
self.formatter?.usesGroupingSeparator = true
self.decimals = decimals
hasAutoDecimals = true
}
@objc public init(block: @escaping Block)
{
super.init()
self.block = block
}
@objc public static func with(block: @escaping Block) -> DefaultAxisValueFormatter?
{
return DefaultAxisValueFormatter(block: block)
}
open func stringForValue(_ value: Double,
axis: AxisBase?) -> String
{
if let block = block {
return block(value, axis)
} else {
return formatter?.string(from: NSNumber(floatLiteral: value)) ?? ""
}
}
}

View File

@@ -0,0 +1,62 @@
//
// DefaultFillFormatter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
/// Default formatter that calculates the position of the filled line.
@objc(ChartDefaultFillFormatter)
open class DefaultFillFormatter: NSObject, IFillFormatter
{
public typealias Block = (
_ dataSet: ILineChartDataSet,
_ dataProvider: LineChartDataProvider) -> CGFloat
@objc open var block: Block?
public override init() { }
@objc public init(block: @escaping Block)
{
self.block = block
}
@objc public static func with(block: @escaping Block) -> DefaultFillFormatter?
{
return DefaultFillFormatter(block: block)
}
open func getFillLinePosition(
dataSet: ILineChartDataSet,
dataProvider: LineChartDataProvider) -> CGFloat
{
guard block == nil else { return block!(dataSet, dataProvider) }
var fillMin: CGFloat = 0.0
if dataSet.yMax > 0.0 && dataSet.yMin < 0.0
{
fillMin = 0.0
}
else if let data = dataProvider.data
{
let max = data.yMax > 0.0 ? 0.0 : dataProvider.chartYMax
let min = data.yMin < 0.0 ? 0.0 : dataProvider.chartYMin
fillMin = CGFloat(dataSet.yMin >= 0.0 ? min : max)
}
return fillMin
}
}

View File

@@ -0,0 +1,103 @@
//
// DefaultValueFormatter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
@objc(ChartDefaultValueFormatter)
open class DefaultValueFormatter: NSObject, IValueFormatter
{
public typealias Block = (
_ value: Double,
_ entry: ChartDataEntry,
_ dataSetIndex: Int,
_ viewPortHandler: ViewPortHandler?) -> String
@objc open var block: Block?
@objc open var hasAutoDecimals: Bool = false
private var _formatter: NumberFormatter?
@objc open var formatter: NumberFormatter?
{
get { return _formatter }
set
{
hasAutoDecimals = false
_formatter = newValue
}
}
private var _decimals: Int?
open var decimals: Int?
{
get { return _decimals }
set
{
_decimals = newValue
if let digits = newValue
{
self.formatter?.minimumFractionDigits = digits
self.formatter?.maximumFractionDigits = digits
self.formatter?.usesGroupingSeparator = true
}
}
}
public override init()
{
super.init()
self.formatter = NumberFormatter()
hasAutoDecimals = true
}
@objc public init(formatter: NumberFormatter)
{
super.init()
self.formatter = formatter
}
@objc public init(decimals: Int)
{
super.init()
self.formatter = NumberFormatter()
self.formatter?.usesGroupingSeparator = true
self.decimals = decimals
hasAutoDecimals = true
}
@objc public init(block: @escaping Block)
{
super.init()
self.block = block
}
@objc public static func with(block: @escaping Block) -> DefaultValueFormatter?
{
return DefaultValueFormatter(block: block)
}
open func stringForValue(_ value: Double,
entry: ChartDataEntry,
dataSetIndex: Int,
viewPortHandler: ViewPortHandler?) -> String
{
if let block = block {
return block(value, entry, dataSetIndex, viewPortHandler)
} else {
return formatter?.string(from: NSNumber(floatLiteral: value)) ?? ""
}
}
}

View File

@@ -0,0 +1,30 @@
//
// IAxisValueFormatter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
/// An interface for providing custom axis Strings.
@objc(IChartAxisValueFormatter)
public protocol IAxisValueFormatter: class
{
/// Called when a value from an axis is formatted before being drawn.
///
/// For performance reasons, avoid excessive calculations and memory allocations inside this method.
///
/// - returns: The customized label that is drawn on the x-axis.
/// - parameter value: the value that is currently being drawn
/// - parameter axis: the axis that the value belongs to
///
func stringForValue(_ value: Double,
axis: AxisBase?) -> String
}

View File

@@ -0,0 +1,21 @@
//
// IFillFormatter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
/// Protocol for providing a custom logic to where the filling line of a LineDataSet should end. This of course only works if setFillEnabled(...) is set to true.
@objc(IChartFillFormatter)
public protocol IFillFormatter
{
/// - returns: The vertical (y-axis) position where the filled-line of the LineDataSet should end.
func getFillLinePosition(dataSet: ILineChartDataSet, dataProvider: LineChartDataProvider) -> CGFloat
}

View File

@@ -0,0 +1,41 @@
//
// IValueFormatter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
/// Interface that allows custom formatting of all values inside the chart before they are being drawn to the screen.
///
/// Simply create your own formatting class and let it implement ValueFormatter.
///
/// Then override the getFormattedValue(...) method and return whatever you want.
@objc(IChartValueFormatter)
public protocol IValueFormatter: class
{
/// Called when a value (from labels inside the chart) is formatted before being drawn.
///
/// For performance reasons, avoid excessive calculations and memory allocations inside this method.
///
/// - returns: The formatted label ready for being drawn
///
/// - parameter value: The value to be formatted
///
/// - parameter axis: The entry the value belongs to - in e.g. BarChart, this is of class BarEntry
///
/// - parameter dataSetIndex: The index of the DataSet the entry in focus belongs to
///
/// - parameter viewPortHandler: provides information about the current chart state (scale, translation, ...)
///
func stringForValue(_ value: Double,
entry: ChartDataEntry,
dataSetIndex: Int,
viewPortHandler: ViewPortHandler?) -> String
}

View File

@@ -0,0 +1,59 @@
//
// IndexAxisValueFormatter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
/// This formatter is used for passing an array of x-axis labels, on whole x steps.
@objc(ChartIndexAxisValueFormatter)
open class IndexAxisValueFormatter: NSObject, IAxisValueFormatter
{
private var _values: [String] = [String]()
private var _valueCount: Int = 0
@objc public var values: [String]
{
get
{
return _values
}
set
{
_values = newValue
_valueCount = _values.count
}
}
public override init()
{
super.init()
}
@objc public init(values: [String])
{
super.init()
self.values = values
}
@objc public static func with(values: [String]) -> IndexAxisValueFormatter?
{
return IndexAxisValueFormatter(values: values)
}
open func stringForValue(_ value: Double,
axis: AxisBase?) -> String
{
let index = Int(value.rounded())
guard values.indices.contains(index), index == Int(value) else { return "" }
return _values[index]
}
}

View File

@@ -0,0 +1,118 @@
//
// BarHighlighter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(BarChartHighlighter)
open class BarHighlighter: ChartHighlighter
{
open override func getHighlight(x: CGFloat, y: CGFloat) -> Highlight?
{
guard
let barData = (self.chart as? BarChartDataProvider)?.barData,
let high = super.getHighlight(x: x, y: y)
else { return nil }
let pos = getValsForTouch(x: x, y: y)
if let set = barData.getDataSetByIndex(high.dataSetIndex) as? IBarChartDataSet,
set.isStacked
{
return getStackedHighlight(high: high,
set: set,
xValue: Double(pos.x),
yValue: Double(pos.y))
}
else
{
return high
}
}
internal override func getDistance(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) -> CGFloat
{
return abs(x1 - x2)
}
internal override var data: ChartData?
{
return (chart as? BarChartDataProvider)?.barData
}
/// This method creates the Highlight object that also indicates which value of a stacked BarEntry has been selected.
/// - parameter high: the Highlight to work with looking for stacked values
/// - parameter set:
/// - parameter xIndex:
/// - parameter yValue:
/// - returns:
@objc open func getStackedHighlight(high: Highlight,
set: IBarChartDataSet,
xValue: Double,
yValue: Double) -> Highlight?
{
guard
let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider,
let entry = set.entryForXValue(xValue, closestToY: yValue) as? BarChartDataEntry
else { return nil }
// Not stacked
if entry.yValues == nil
{
return high
}
guard
let ranges = entry.ranges,
ranges.count > 0
else { return nil }
let stackIndex = getClosestStackIndex(ranges: ranges, value: yValue)
let pixel = chart
.getTransformer(forAxis: set.axisDependency)
.pixelForValues(x: high.x, y: ranges[stackIndex].to)
return Highlight(x: entry.x,
y: entry.y,
xPx: pixel.x,
yPx: pixel.y,
dataSetIndex: high.dataSetIndex,
stackIndex: stackIndex,
axis: high.axis)
}
/// - returns: The index of the closest value inside the values array / ranges (stacked barchart) to the value given as a parameter.
/// - parameter entry:
/// - parameter value:
/// - returns:
@objc open func getClosestStackIndex(ranges: [Range]?, value: Double) -> Int
{
guard let ranges = ranges else { return 0 }
var stackIndex = 0
for range in ranges
{
if range.contains(value)
{
return stackIndex
}
else
{
stackIndex += 1
}
}
let length = max(ranges.count - 1, 0)
return (value > ranges[length].to) ? length : 0
}
}

View File

@@ -0,0 +1,188 @@
//
// ChartHighlighter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class ChartHighlighter : NSObject, IHighlighter
{
/// instance of the data-provider
@objc open weak var chart: ChartDataProvider?
@objc public init(chart: ChartDataProvider)
{
self.chart = chart
}
open func getHighlight(x: CGFloat, y: CGFloat) -> Highlight?
{
let xVal = Double(getValsForTouch(x: x, y: y).x)
return getHighlight(xValue: xVal, x: x, y: y)
}
/// - returns: The corresponding x-pos for a given touch-position in pixels.
/// - parameter x:
/// - returns:
@objc open func getValsForTouch(x: CGFloat, y: CGFloat) -> CGPoint
{
guard let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider else { return .zero }
// take any transformer to determine the values
return chart.getTransformer(forAxis: .left).valueForTouchPoint(x: x, y: y)
}
/// - returns: The corresponding ChartHighlight for a given x-value and xy-touch position in pixels.
/// - parameter xValue:
/// - parameter x:
/// - parameter y:
/// - returns:
@objc open func getHighlight(xValue xVal: Double, x: CGFloat, y: CGFloat) -> Highlight?
{
guard let chart = chart else { return nil }
let closestValues = getHighlights(xValue: xVal, x: x, y: y)
guard !closestValues.isEmpty else { return nil }
let leftAxisMinDist = getMinimumDistance(closestValues: closestValues, y: y, axis: .left)
let rightAxisMinDist = getMinimumDistance(closestValues: closestValues, y: y, axis: .right)
let axis: YAxis.AxisDependency = leftAxisMinDist < rightAxisMinDist ? .left : .right
let detail = closestSelectionDetailByPixel(closestValues: closestValues, x: x, y: y, axis: axis, minSelectionDistance: chart.maxHighlightDistance)
return detail
}
/// - returns: A list of Highlight objects representing the entries closest to the given xVal.
/// The returned list contains two objects per DataSet (closest rounding up, closest rounding down).
/// - parameter xValue: the transformed x-value of the x-touch position
/// - parameter x: touch position
/// - parameter y: touch position
/// - returns:
@objc open func getHighlights(xValue: Double, x: CGFloat, y: CGFloat) -> [Highlight]
{
var vals = [Highlight]()
guard let data = self.data else { return vals }
for i in 0 ..< data.dataSetCount
{
guard
let dataSet = data.getDataSetByIndex(i),
dataSet.isHighlightEnabled // don't include datasets that cannot be highlighted
else { continue }
// extract all y-values from all DataSets at the given x-value.
// some datasets (i.e bubble charts) make sense to have multiple values for an x-value. We'll have to find a way to handle that later on. It's more complicated now when x-indices are floating point.
vals.append(contentsOf: buildHighlights(dataSet: dataSet, dataSetIndex: i, xValue: xValue, rounding: .closest))
}
return vals
}
/// - returns: An array of `Highlight` objects corresponding to the selected xValue and dataSetIndex.
internal func buildHighlights(
dataSet set: IChartDataSet,
dataSetIndex: Int,
xValue: Double,
rounding: ChartDataSetRounding) -> [Highlight]
{
var highlights = [Highlight]()
guard let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider else { return highlights }
var entries = set.entriesForXValue(xValue)
if entries.count == 0, let closest = set.entryForXValue(xValue, closestToY: .nan, rounding: rounding)
{
// Try to find closest x-value and take all entries for that x-value
entries = set.entriesForXValue(closest.x)
}
for e in entries
{
let px = chart.getTransformer(forAxis: set.axisDependency).pixelForValues(x: e.x, y: e.y)
let highlight = Highlight(x: e.x, y: e.y, xPx: px.x, yPx: px.y, dataSetIndex: dataSetIndex, axis: set.axisDependency)
highlights.append(highlight)
}
return highlights
}
// - MARK: - Utilities
/// - returns: The `ChartHighlight` of the closest value on the x-y cartesian axes
internal func closestSelectionDetailByPixel(
closestValues: [Highlight],
x: CGFloat,
y: CGFloat,
axis: YAxis.AxisDependency?,
minSelectionDistance: CGFloat) -> Highlight?
{
var distance = minSelectionDistance
var closest: Highlight?
for high in closestValues
{
if axis == nil || high.axis == axis
{
let cDistance = getDistance(x1: x, y1: y, x2: high.xPx, y2: high.yPx)
if cDistance < distance
{
closest = high
distance = cDistance
}
}
}
return closest
}
/// - returns: The minimum distance from a touch-y-value (in pixels) to the closest y-value (in pixels) that is displayed in the chart.
internal func getMinimumDistance(
closestValues: [Highlight],
y: CGFloat,
axis: YAxis.AxisDependency) -> CGFloat
{
var distance = CGFloat.greatestFiniteMagnitude
for high in closestValues
{
if high.axis == axis
{
let tempDistance = abs(getHighlightPos(high: high) - y)
if tempDistance < distance
{
distance = tempDistance
}
}
}
return distance
}
internal func getHighlightPos(high: Highlight) -> CGFloat
{
return high.yPx
}
internal func getDistance(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) -> CGFloat
{
return hypot(x1 - x2, y1 - y2)
}
internal var data: ChartData?
{
return chart?.data
}
}

View File

@@ -0,0 +1,70 @@
//
// CombinedHighlighter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(CombinedChartHighlighter)
open class CombinedHighlighter: ChartHighlighter
{
/// bar highlighter for supporting stacked highlighting
private var barHighlighter: BarHighlighter?
@objc public init(chart: CombinedChartDataProvider, barDataProvider: BarChartDataProvider)
{
super.init(chart: chart)
// if there is BarData, create a BarHighlighter
self.barHighlighter = barDataProvider.barData == nil ? nil : BarHighlighter(chart: barDataProvider)
}
open override func getHighlights(xValue: Double, x: CGFloat, y: CGFloat) -> [Highlight]
{
var vals = [Highlight]()
guard
let chart = self.chart as? CombinedChartDataProvider,
let dataObjects = chart.combinedData?.allData
else { return vals }
for i in 0..<dataObjects.count
{
let dataObject = dataObjects[i]
// in case of BarData, let the BarHighlighter take over
if barHighlighter != nil && dataObject is BarChartData,
let high = barHighlighter?.getHighlight(x: x, y: y)
{
high.dataIndex = i
vals.append(high)
}
else
{
for j in 0..<dataObject.dataSetCount
{
guard let dataSet = dataObject.getDataSetByIndex(j),
dataSet.isHighlightEnabled // don't include datasets that cannot be highlighted
else { continue }
let highs = buildHighlights(dataSet: dataSet, dataSetIndex: j, xValue: xValue, rounding: .closest)
for high in highs
{
high.dataIndex = i
vals.append(high)
}
}
}
}
return vals
}
}

View File

@@ -0,0 +1,198 @@
//
// Highlight.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(ChartHighlight)
open class Highlight: NSObject
{
/// the x-value of the highlighted value
fileprivate var _x = Double.nan
/// the y-value of the highlighted value
fileprivate var _y = Double.nan
/// the x-pixel of the highlight
private var _xPx = CGFloat.nan
/// the y-pixel of the highlight
private var _yPx = CGFloat.nan
/// the index of the data object - in case it refers to more than one
@objc open var dataIndex = Int(-1)
/// the index of the dataset the highlighted value is in
fileprivate var _dataSetIndex = Int(0)
/// index which value of a stacked bar entry is highlighted
///
/// **default**: -1
fileprivate var _stackIndex = Int(-1)
/// the axis the highlighted value belongs to
private var _axis: YAxis.AxisDependency = YAxis.AxisDependency.left
/// the x-position (pixels) on which this highlight object was last drawn
@objc open var drawX: CGFloat = 0.0
/// the y-position (pixels) on which this highlight object was last drawn
@objc open var drawY: CGFloat = 0.0
public override init()
{
super.init()
}
/// - parameter x: the x-value of the highlighted value
/// - parameter y: the y-value of the highlighted value
/// - parameter xPx: the x-pixel of the highlighted value
/// - parameter yPx: the y-pixel of the highlighted value
/// - parameter dataIndex: the index of the Data the highlighted value belongs to
/// - parameter dataSetIndex: the index of the DataSet the highlighted value belongs to
/// - parameter stackIndex: references which value of a stacked-bar entry has been selected
/// - parameter axis: the axis the highlighted value belongs to
@objc public init(
x: Double, y: Double,
xPx: CGFloat, yPx: CGFloat,
dataIndex: Int,
dataSetIndex: Int,
stackIndex: Int,
axis: YAxis.AxisDependency)
{
super.init()
_x = x
_y = y
_xPx = xPx
_yPx = yPx
self.dataIndex = dataIndex
_dataSetIndex = dataSetIndex
_stackIndex = stackIndex
_axis = axis
}
/// - parameter x: the x-value of the highlighted value
/// - parameter y: the y-value of the highlighted value
/// - parameter xPx: the x-pixel of the highlighted value
/// - parameter yPx: the y-pixel of the highlighted value
/// - parameter dataSetIndex: the index of the DataSet the highlighted value belongs to
/// - parameter stackIndex: references which value of a stacked-bar entry has been selected
/// - parameter axis: the axis the highlighted value belongs to
@objc public convenience init(
x: Double, y: Double,
xPx: CGFloat, yPx: CGFloat,
dataSetIndex: Int,
stackIndex: Int,
axis: YAxis.AxisDependency)
{
self.init(x: x, y: y, xPx: xPx, yPx: yPx,
dataIndex: 0,
dataSetIndex: dataSetIndex,
stackIndex: stackIndex,
axis: axis)
}
/// - parameter x: the x-value of the highlighted value
/// - parameter y: the y-value of the highlighted value
/// - parameter xPx: the x-pixel of the highlighted value
/// - parameter yPx: the y-pixel of the highlighted value
/// - parameter dataIndex: the index of the Data the highlighted value belongs to
/// - parameter dataSetIndex: the index of the DataSet the highlighted value belongs to
/// - parameter stackIndex: references which value of a stacked-bar entry has been selected
/// - parameter axis: the axis the highlighted value belongs to
@objc public init(
x: Double, y: Double,
xPx: CGFloat, yPx: CGFloat,
dataSetIndex: Int,
axis: YAxis.AxisDependency)
{
super.init()
_x = x
_y = y
_xPx = xPx
_yPx = yPx
_dataSetIndex = dataSetIndex
_axis = axis
}
/// - parameter x: the x-value of the highlighted value
/// - parameter y: the y-value of the highlighted value
/// - parameter dataSetIndex: the index of the DataSet the highlighted value belongs to
/// - parameter dataIndex: The data index to search in (only used in CombinedChartView currently)
@objc public init(x: Double, y: Double, dataSetIndex: Int, dataIndex: Int = -1)
{
_x = x
_y = y
_dataSetIndex = dataSetIndex
self.dataIndex = dataIndex
}
/// - parameter x: the x-value of the highlighted value
/// - parameter dataSetIndex: the index of the DataSet the highlighted value belongs to
/// - parameter stackIndex: references which value of a stacked-bar entry has been selected
@objc public convenience init(x: Double, dataSetIndex: Int, stackIndex: Int)
{
self.init(x: x, y: Double.nan, dataSetIndex: dataSetIndex)
_stackIndex = stackIndex
}
@objc open var x: Double { return _x }
@objc open var y: Double { return _y }
@objc open var xPx: CGFloat { return _xPx }
@objc open var yPx: CGFloat { return _yPx }
@objc open var dataSetIndex: Int { return _dataSetIndex }
@objc open var stackIndex: Int { return _stackIndex }
@objc open var axis: YAxis.AxisDependency { return _axis }
@objc open var isStacked: Bool { return _stackIndex >= 0 }
/// Sets the x- and y-position (pixels) where this highlight was last drawn.
@objc open func setDraw(x: CGFloat, y: CGFloat)
{
self.drawX = x
self.drawY = y
}
/// Sets the x- and y-position (pixels) where this highlight was last drawn.
@objc open func setDraw(pt: CGPoint)
{
self.drawX = pt.x
self.drawY = pt.y
}
// MARK: NSObject
open override var description: String
{
return "Highlight, x: \(_x), y: \(_y), dataIndex (combined charts): \(dataIndex), dataSetIndex: \(_dataSetIndex), stackIndex (only stacked barentry): \(_stackIndex)"
}
}
// MARK: Equatable
extension Highlight /*: Equatable*/ {
open override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Highlight else { return false }
if self === object
{
return true
}
return _x == object._x
&& _y == object._y
&& dataIndex == object.dataIndex
&& _dataSetIndex == object._dataSetIndex
&& _stackIndex == object._stackIndex
}
}

View File

@@ -0,0 +1,68 @@
//
// HorizontalBarHighlighter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(HorizontalBarChartHighlighter)
open class HorizontalBarHighlighter: BarHighlighter
{
open override func getHighlight(x: CGFloat, y: CGFloat) -> Highlight?
{
guard let barData = self.chart?.data as? BarChartData else { return nil }
let pos = getValsForTouch(x: y, y: x)
guard let high = getHighlight(xValue: Double(pos.y), x: y, y: x) else { return nil }
if let set = barData.getDataSetByIndex(high.dataSetIndex) as? IBarChartDataSet,
set.isStacked
{
return getStackedHighlight(high: high,
set: set,
xValue: Double(pos.y),
yValue: Double(pos.x))
}
return high
}
internal override func buildHighlights(
dataSet set: IChartDataSet,
dataSetIndex: Int,
xValue: Double,
rounding: ChartDataSetRounding) -> [Highlight]
{
var highlights = [Highlight]()
guard let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider else { return highlights }
var entries = set.entriesForXValue(xValue)
if entries.count == 0, let closest = set.entryForXValue(xValue, closestToY: .nan, rounding: rounding)
{
// Try to find closest x-value and take all entries for that x-value
entries = set.entriesForXValue(closest.x)
}
for e in entries
{
let px = chart.getTransformer(forAxis: set.axisDependency).pixelForValues(x: e.y, y: e.x)
highlights.append(Highlight(x: e.x, y: e.y, xPx: px.x, yPx: px.y, dataSetIndex: dataSetIndex, axis: set.axisDependency))
}
return highlights
}
internal override func getDistance(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) -> CGFloat
{
return abs(y1 - y2)
}
}

View File

@@ -0,0 +1,23 @@
//
// IHighlighter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(IChartHighlighter)
public protocol IHighlighter: class
{
/// - returns: A Highlight object corresponding to the given x- and y- touch positions in pixels.
/// - parameter x:
/// - parameter y:
/// - returns:
func getHighlight(x: CGFloat, y: CGFloat) -> Highlight?
}

View File

@@ -0,0 +1,27 @@
//
// PieHighlighter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(PieChartHighlighter)
open class PieHighlighter: PieRadarHighlighter
{
open override func closestHighlight(index: Int, x: CGFloat, y: CGFloat) -> Highlight?
{
guard
let set = chart?.data?.dataSets[0],
let entry = set.entryForIndex(index)
else { return nil }
return Highlight(x: Double(index), y: entry.y, xPx: x, yPx: y, dataSetIndex: 0, axis: set.axisDependency)
}
}

View File

@@ -0,0 +1,60 @@
//
// PieRadarHighlighter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(PieRadarChartHighlighter)
open class PieRadarHighlighter: ChartHighlighter
{
open override func getHighlight(x: CGFloat, y: CGFloat) -> Highlight?
{
guard let chart = self.chart as? PieRadarChartViewBase else { return nil }
let touchDistanceToCenter = chart.distanceToCenter(x: x, y: y)
// check if a slice was touched
guard touchDistanceToCenter <= chart.radius else
{
// if no slice was touched, highlight nothing
return nil
}
var angle = chart.angleForPoint(x: x ,y: y)
if chart is PieChartView
{
angle /= CGFloat(chart.chartAnimator.phaseY)
}
let index = chart.indexForAngle(angle)
// check if the index could be found
if index < 0 || index >= chart.data?.maxEntryCountSet?.entryCount ?? 0
{
return nil
}
else
{
return closestHighlight(index: index, x: x, y: y)
}
}
/// - returns: The closest Highlight object of the given objects based on the touch position inside the chart.
/// - parameter index:
/// - parameter x:
/// - parameter y:
@objc open func closestHighlight(index: Int, x: CGFloat, y: CGFloat) -> Highlight?
{
fatalError("closestHighlight(index, x, y) cannot be called on PieRadarChartHighlighter")
}
}

View File

@@ -0,0 +1,78 @@
//
// RadarHighlighter.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc(RadarChartHighlighter)
open class RadarHighlighter: PieRadarHighlighter
{
open override func closestHighlight(index: Int, x: CGFloat, y: CGFloat) -> Highlight?
{
guard let chart = self.chart as? RadarChartView else { return nil }
let highlights = getHighlights(forIndex: index)
let distanceToCenter = Double(chart.distanceToCenter(x: x, y: y) / chart.factor)
var closest: Highlight?
var distance = Double.greatestFiniteMagnitude
for high in highlights
{
let cdistance = abs(high.y - distanceToCenter)
if cdistance < distance
{
closest = high
distance = cdistance
}
}
return closest
}
/// - returns: An array of Highlight objects for the given index.
/// The Highlight objects give information about the value at the selected index and DataSet it belongs to.
///
/// - parameter index:
internal func getHighlights(forIndex index: Int) -> [Highlight]
{
var vals = [Highlight]()
guard
let chart = self.chart as? RadarChartView,
let chartData = chart.data
else { return vals }
let phaseX = chart.chartAnimator.phaseX
let phaseY = chart.chartAnimator.phaseY
let sliceangle = chart.sliceAngle
let factor = chart.factor
for i in chartData.dataSets.indices
{
guard
let dataSet = chartData.getDataSetByIndex(i),
let entry = dataSet.entryForIndex(index)
else { continue }
let y = (entry.y - chart.chartYMin)
let p = chart.centerOffsets.moving(distance: CGFloat(y) * factor * CGFloat(phaseY),
atAngle: sliceangle * CGFloat(index) * CGFloat(phaseX) + chart.rotationAngle)
let highlight = Highlight(x: Double(index), y: entry.y, xPx: p.x, yPx: p.y, dataSetIndex: i, axis: dataSet.axisDependency)
vals.append(highlight)
}
return vals
}
}

View File

@@ -0,0 +1,51 @@
//
// Range.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
@objc(ChartRange)
open class Range: NSObject
{
@objc open var from: Double
@objc open var to: Double
@objc public init(from: Double, to: Double)
{
self.from = from
self.to = to
super.init()
}
/// - returns: `true` if this range contains (if the value is in between) the given value, `false` ifnot.
/// - parameter value:
@objc open func contains(_ value: Double) -> Bool
{
if value > from && value <= to
{
return true
}
else
{
return false
}
}
@objc open func isLarger(_ value: Double) -> Bool
{
return value > to
}
@objc open func isSmaller(_ value: Double) -> Bool
{
return value < from
}
}

View File

@@ -0,0 +1,23 @@
//
// BarChartDataProvider.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol BarChartDataProvider: BarLineScatterCandleBubbleChartDataProvider
{
var barData: BarChartData? { get }
var isDrawBarShadowEnabled: Bool { get }
var isDrawValueAboveBarEnabled: Bool { get }
var isHighlightFullBarEnabled: Bool { get }
}

View File

@@ -0,0 +1,23 @@
//
// BarLineScatterCandleBubbleChartDataProvider.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol BarLineScatterCandleBubbleChartDataProvider: ChartDataProvider
{
func getTransformer(forAxis: YAxis.AxisDependency) -> Transformer
func isInverted(axis: YAxis.AxisDependency) -> Bool
var lowestVisibleX: Double { get }
var highestVisibleX: Double { get }
}

View File

@@ -0,0 +1,19 @@
//
// BubbleChartDataProvider.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol BubbleChartDataProvider: BarLineScatterCandleBubbleChartDataProvider
{
var bubbleData: BubbleChartData? { get }
}

View File

@@ -0,0 +1,19 @@
//
// CandleChartDataProvider.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol CandleChartDataProvider: BarLineScatterCandleBubbleChartDataProvider
{
var candleData: CandleChartData? { get }
}

View File

@@ -0,0 +1,39 @@
//
// ChartDataProvider.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol ChartDataProvider
{
/// - returns: The minimum x-value of the chart, regardless of zoom or translation.
var chartXMin: Double { get }
/// - returns: The maximum x-value of the chart, regardless of zoom or translation.
var chartXMax: Double { get }
/// - returns: The minimum y-value of the chart, regardless of zoom or translation.
var chartYMin: Double { get }
/// - returns: The maximum y-value of the chart, regardless of zoom or translation.
var chartYMax: Double { get }
var maxHighlightDistance: CGFloat { get }
var xRange: Double { get }
var centerOffsets: CGPoint { get }
var data: ChartData? { get }
var maxVisibleCount: Int { get }
}

View File

@@ -0,0 +1,19 @@
//
// CombinedChartDataProvider.swoft
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol CombinedChartDataProvider: LineChartDataProvider, BarChartDataProvider, BubbleChartDataProvider, CandleChartDataProvider, ScatterChartDataProvider
{
var combinedData: CombinedChartData? { get }
}

View File

@@ -0,0 +1,21 @@
//
// LineChartDataProvider.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol LineChartDataProvider: BarLineScatterCandleBubbleChartDataProvider
{
var lineData: LineChartData? { get }
func getAxis(_ axis: YAxis.AxisDependency) -> YAxis
}

View File

@@ -0,0 +1,19 @@
//
// ScatterChartDataProvider.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
@objc
public protocol ScatterChartDataProvider: BarLineScatterCandleBubbleChartDataProvider
{
var scatterData: ScatterChartData? { get }
}

View File

@@ -0,0 +1,37 @@
//
// AnimatedMoveViewJob.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
open class AnimatedMoveViewJob: AnimatedViewPortJob
{
internal override func animationUpdate()
{
guard
let viewPortHandler = viewPortHandler,
let transformer = transformer,
let view = view
else { return }
var pt = CGPoint(
x: xOrigin + (CGFloat(xValue) - xOrigin) * phase,
y: yOrigin + (CGFloat(yValue) - yOrigin) * phase
)
transformer.pointValueToPixel(&pt)
viewPortHandler.centerViewPort(pt: pt, chart: view)
}
}

View File

@@ -0,0 +1,130 @@
//
// AnimatedViewPortJob.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
open class AnimatedViewPortJob: ViewPortJob
{
internal var phase: CGFloat = 1.0
internal var xOrigin: CGFloat = 0.0
internal var yOrigin: CGFloat = 0.0
private var _startTime: TimeInterval = 0.0
private var _displayLink: NSUIDisplayLink!
private var _duration: TimeInterval = 0.0
private var _endTime: TimeInterval = 0.0
private var _easing: ChartEasingFunctionBlock?
@objc public init(
viewPortHandler: ViewPortHandler,
xValue: Double,
yValue: Double,
transformer: Transformer,
view: ChartViewBase,
xOrigin: CGFloat,
yOrigin: CGFloat,
duration: TimeInterval,
easing: ChartEasingFunctionBlock?)
{
super.init(viewPortHandler: viewPortHandler,
xValue: xValue,
yValue: yValue,
transformer: transformer,
view: view)
self.xOrigin = xOrigin
self.yOrigin = yOrigin
self._duration = duration
self._easing = easing
}
deinit
{
stop(finish: false)
}
open override func doJob()
{
start()
}
@objc open func start()
{
_startTime = CACurrentMediaTime()
_endTime = _startTime + _duration
_endTime = _endTime > _endTime ? _endTime : _endTime
updateAnimationPhase(_startTime)
_displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop))
_displayLink.add(to: .main, forMode: .commonModes)
}
@objc open func stop(finish: Bool)
{
guard _displayLink != nil else { return }
_displayLink.remove(from: .main, forMode: .commonModes)
_displayLink = nil
if finish
{
if phase != 1.0
{
phase = 1.0
animationUpdate()
}
animationEnd()
}
}
private func updateAnimationPhase(_ currentTime: TimeInterval)
{
let elapsedTime = currentTime - _startTime
let duration = _duration
var elapsed = elapsedTime
elapsed = min(elapsed, duration)
phase = CGFloat(_easing?(elapsed, duration) ?? elapsed / duration)
}
@objc private func animationLoop()
{
let currentTime: TimeInterval = CACurrentMediaTime()
updateAnimationPhase(currentTime)
animationUpdate()
if currentTime >= _endTime
{
stop(finish: true)
}
}
internal func animationUpdate()
{
// Override this
}
internal func animationEnd()
{
// Override this
}
}

View File

@@ -0,0 +1,96 @@
//
// AnimatedZoomViewJob.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
open class AnimatedZoomViewJob: AnimatedViewPortJob
{
internal var yAxis: YAxis?
internal var xAxisRange: Double = 0.0
internal var scaleX: CGFloat = 0.0
internal var scaleY: CGFloat = 0.0
internal var zoomOriginX: CGFloat = 0.0
internal var zoomOriginY: CGFloat = 0.0
internal var zoomCenterX: CGFloat = 0.0
internal var zoomCenterY: CGFloat = 0.0
@objc public init(
viewPortHandler: ViewPortHandler,
transformer: Transformer,
view: ChartViewBase,
yAxis: YAxis,
xAxisRange: Double,
scaleX: CGFloat,
scaleY: CGFloat,
xOrigin: CGFloat,
yOrigin: CGFloat,
zoomCenterX: CGFloat,
zoomCenterY: CGFloat,
zoomOriginX: CGFloat,
zoomOriginY: CGFloat,
duration: TimeInterval,
easing: ChartEasingFunctionBlock?)
{
super.init(viewPortHandler: viewPortHandler,
xValue: 0.0,
yValue: 0.0,
transformer: transformer,
view: view,
xOrigin: xOrigin,
yOrigin: yOrigin,
duration: duration,
easing: easing)
self.yAxis = yAxis
self.xAxisRange = xAxisRange
self.scaleX = scaleX
self.scaleY = scaleY
self.zoomCenterX = zoomCenterX
self.zoomCenterY = zoomCenterY
self.zoomOriginX = zoomOriginX
self.zoomOriginY = zoomOriginY
}
internal override func animationUpdate()
{
guard
let viewPortHandler = viewPortHandler,
let transformer = transformer,
let view = view
else { return }
let scaleX = xOrigin + (self.scaleX - xOrigin) * phase
let scaleY = yOrigin + (self.scaleY - yOrigin) * phase
var matrix = viewPortHandler.setZoom(scaleX: scaleX, scaleY: scaleY)
viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: false)
let valsInView = CGFloat(yAxis?.axisRange ?? 0.0) / viewPortHandler.scaleY
let xsInView = CGFloat(xAxisRange) / viewPortHandler.scaleX
var pt = CGPoint(
x: zoomOriginX + ((zoomCenterX - xsInView / 2.0) - zoomOriginX) * phase,
y: zoomOriginY + ((zoomCenterY + valsInView / 2.0) - zoomOriginY) * phase
)
transformer.pointValueToPixel(&pt)
matrix = viewPortHandler.translate(pt: pt)
viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: true)
}
internal override func animationEnd()
{
(view as? BarLineChartViewBase)?.calculateOffsets()
view?.setNeedsDisplay()
}
}

View File

@@ -0,0 +1,38 @@
//
// MoveViewJob.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
@objc(MoveChartViewJob)
open class MoveViewJob: ViewPortJob
{
open override func doJob()
{
guard
let viewPortHandler = viewPortHandler,
let transformer = transformer,
let view = view
else { return }
var pt = CGPoint(
x: xValue,
y: yValue
)
transformer.pointValueToPixel(&pt)
viewPortHandler.centerViewPort(pt: pt, chart: view)
}
}

Some files were not shown because too many files have changed in this diff Show More