diff --git a/.swift-version b/.swift-version index 6e63660..11aa145 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.0 \ No newline at end of file +5.3 \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml index 8ffbe26..b7e7e83 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,5 +1,6 @@ disabled_rules: - - trailing_comma + - trailing_comma # swiftformat is not copacetic + - opening_brace # swiftformat is not copacetic opt_in_rules: - empty_count - empty_string diff --git a/Carthage/Checkouts/LaunchAtLogin/LaunchAtLogin/LaunchAtLogin.swift b/Carthage/Checkouts/LaunchAtLogin/LaunchAtLogin/LaunchAtLogin.swift index ba36c3e..5b169ac 100644 --- a/Carthage/Checkouts/LaunchAtLogin/LaunchAtLogin/LaunchAtLogin.swift +++ b/Carthage/Checkouts/LaunchAtLogin/LaunchAtLogin/LaunchAtLogin.swift @@ -1,7 +1,7 @@ import Foundation import ServiceManagement -public struct LaunchAtLogin { +public enum LaunchAtLogin { private static let id = "\(Bundle.main.bundleIdentifier!)-LaunchAtLoginHelper" public static var isEnabled: Bool { diff --git a/PiBar/Data Sources/PiholeAPI.swift b/PiBar Shared/Data Sources/PiholeAPI.swift similarity index 77% rename from PiBar/Data Sources/PiholeAPI.swift rename to PiBar Shared/Data Sources/PiholeAPI.swift index 75495b3..d62f788 100644 --- a/PiBar/Data Sources/PiholeAPI.swift +++ b/PiBar Shared/Data Sources/PiholeAPI.swift @@ -9,13 +9,17 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -import Cocoa +#if os(macOS) + import Cocoa +#elseif os(iOS) + import UIKit +#endif -class PiholeAPI: NSObject { +final class PiholeAPI { let connection: PiholeConnectionV2 var identifier: String { - return "\(connection.hostname)" + "\(connection.hostname)" } private let path: String = "/admin/api.php" @@ -29,9 +33,11 @@ class PiholeAPI: NSObject { static let enable = PiholeAPIEndpoint(queryParameter: "enable", authorizationRequired: true) static let disable = PiholeAPIEndpoint(queryParameter: "disable", authorizationRequired: true) static let recentBlocked = PiholeAPIEndpoint(queryParameter: "recentBlocked", authorizationRequired: false) + static let getAllQueries = PiholeAPIEndpoint(queryParameter: "getAllQueries", authorizationRequired: true) + } - override init() { + init() { connection = PiholeConnectionV2( hostname: "pi.hole", port: 80, @@ -40,12 +46,10 @@ class PiholeAPI: NSObject { passwordProtected: true, adminPanelURL: "http://pi.hole/admin/" ) - super.init() } init(connection: PiholeConnectionV2) { self.connection = connection - super.init() } private func get(_ endpoint: PiholeAPIEndpoint, argument: String? = nil, completion: @escaping (String?) -> Void) { @@ -98,6 +102,7 @@ class PiholeAPI: NSObject { let object = try jsonDecoder.decode(T.self, from: jsonData) return object } catch { + Log.debug(error.localizedDescription) return nil } } @@ -110,7 +115,7 @@ class PiholeAPI: NSObject { } var admin: URL { - return URL(string: "http://\(connection.hostname):\(connection.port)/admin")! + URL(string: "http://\(connection.hostname):\(connection.port)/admin")! } // MARK: - Testing @@ -137,7 +142,7 @@ class PiholeAPI: NSObject { DispatchQueue.global(qos: .background).async { self.get(Endpoints.summary) { string in guard let jsonString = string, - let summary: PiholeAPISummary = self.decodeJSON(jsonString) else { return completion(nil) } + let summary: PiholeAPISummary = self.decodeJSON(jsonString) else { return completion(nil) } completion(summary) } } @@ -153,6 +158,27 @@ class PiholeAPI: NSObject { } } + func fetchOverTimeData(completion: @escaping (PiholeOverTimeData?) -> Void) { + DispatchQueue.global(qos: .background).async { + self.get(Endpoints.overTimeData10mins) { string in + guard let jsonString = string, + let overTimeData: PiholeOverTimeData = self.decodeJSON(jsonString) else { return completion(nil) } + completion(overTimeData) + } + } + } + + func fetchQueries(count: Int = 100, completion: @escaping (PiholeQueriesData?) -> Void) { + DispatchQueue.global(qos: .background).async { + let countString = String(count) + self.get(Endpoints.getAllQueries, argument: countString) { (string) in + guard let jsonString = string, + let allQueriesData: PiholeQueriesData = self.decodeJSON(jsonString) else { return completion(nil) } + completion(allQueriesData) + } + } + } + func disable(seconds: Int? = nil, completion: @escaping (Bool) -> Void) { DispatchQueue.global(qos: .background).async { var secondsString: String? @@ -161,7 +187,7 @@ class PiholeAPI: NSObject { } self.get(Endpoints.disable, argument: secondsString) { string in guard let jsonString = string, - let _: PiholeAPIStatus = self.decodeJSON(jsonString) else { return completion(false) } + let _: PiholeAPIStatus = self.decodeJSON(jsonString) else { return completion(false) } completion(true) } } @@ -171,7 +197,7 @@ class PiholeAPI: NSObject { DispatchQueue.global(qos: .background).async { self.get(Endpoints.enable) { string in guard let jsonString = string, - let _: PiholeAPIStatus = self.decodeJSON(jsonString) else { return completion(false) } + let _: PiholeAPIStatus = self.decodeJSON(jsonString) else { return completion(false) } completion(true) } } diff --git a/PiBar/Data Sources/Preferences.swift b/PiBar Shared/Data Sources/Preferences.swift similarity index 78% rename from PiBar/Data Sources/Preferences.swift rename to PiBar Shared/Data Sources/Preferences.swift index a5dc7fd..3db85c9 100644 --- a/PiBar/Data Sources/Preferences.swift +++ b/PiBar Shared/Data Sources/Preferences.swift @@ -22,6 +22,9 @@ struct Preferences { static let verboseLabels = "verboseLabels" static let shortcutEnabled = "shortcutEnabled" static let pollingRate = "pollingRate" + static let interfaceColor = "interfaceColor" + static let normalizeCharts = "normalizeCharts" + static let defaultDisableDuration = "defaultDisableDuration" } static var standard: UserDefaults { @@ -36,6 +39,9 @@ struct Preferences { Key.verboseLabels: false, Key.shortcutEnabled: true, Key.pollingRate: 3, + Key.interfaceColor: "red", + Key.normalizeCharts: true, + Key.defaultDisableDuration: -1, ]) return database @@ -94,7 +100,7 @@ extension UserDefaults { } var showBlocked: Bool { - return bool(forKey: Preferences.Key.showBlocked) + bool(forKey: Preferences.Key.showBlocked) } func set(showBlocked: Bool) { @@ -102,7 +108,7 @@ extension UserDefaults { } var showQueries: Bool { - return bool(forKey: Preferences.Key.showQueries) + bool(forKey: Preferences.Key.showQueries) } func set(showQueries: Bool) { @@ -110,7 +116,7 @@ extension UserDefaults { } var showPercentage: Bool { - return bool(forKey: Preferences.Key.showPercentage) + bool(forKey: Preferences.Key.showPercentage) } func set(showPercentage: Bool) { @@ -118,7 +124,7 @@ extension UserDefaults { } var showLabels: Bool { - return bool(forKey: Preferences.Key.showLabels) + bool(forKey: Preferences.Key.showLabels) } func set(showLabels: Bool) { @@ -126,7 +132,7 @@ extension UserDefaults { } var verboseLabels: Bool { - return bool(forKey: Preferences.Key.verboseLabels) + bool(forKey: Preferences.Key.verboseLabels) } func set(verboseLabels: Bool) { @@ -134,7 +140,7 @@ extension UserDefaults { } var shortcutEnabled: Bool { - return bool(forKey: Preferences.Key.shortcutEnabled) + bool(forKey: Preferences.Key.shortcutEnabled) } func set(shortcutEnabled: Bool) { @@ -154,15 +160,40 @@ extension UserDefaults { set(pollingRate, for: Preferences.Key.pollingRate) } + var interfaceColor: String { + string(forKey: Preferences.Key.interfaceColor) ?? "red" + } + + func set(interfaceColor: String) { + set(interfaceColor, for: Preferences.Key.interfaceColor) + } + + var normalizeCharts: Bool { + bool(forKey: Preferences.Key.normalizeCharts) + } + + func set(normalizeCharts: Bool) { + set(normalizeCharts, for: Preferences.Key.normalizeCharts) + } + + var defaultDisableDuration: Int { + integer(forKey: Preferences.Key.defaultDisableDuration) + } + + func set(defaultDisableDuration: Int) { + set(defaultDisableDuration, for: Preferences.Key.defaultDisableDuration) + } + // Helpers var showTitle: Bool { - return showQueries || showBlocked || showPercentage + showQueries || showBlocked || showPercentage } } private extension UserDefaults { func set(_ object: Any?, for key: String) { + Log.debug("Saving \(String(describing: object)) to \(key)") set(object, forKey: key) synchronize() } diff --git a/PiBar/Data Sources/Structs.swift b/PiBar Shared/Data Sources/Structs.swift similarity index 89% rename from PiBar/Data Sources/Structs.swift rename to PiBar Shared/Data Sources/Structs.swift index 9aa49a9..bfca46a 100644 --- a/PiBar/Data Sources/Structs.swift +++ b/PiBar Shared/Data Sources/Structs.swift @@ -110,6 +110,15 @@ struct PiholeAPIStatus: Decodable { let status: String } +struct PiholeOverTimeData: Decodable { + let domainsOverTime: [String: Int] + let adsOverTime: [String: Int] +} + +struct PiholeQueriesData: Decodable { + let data: [[String]] +} + // MARK: - Pi-hole Network enum PiholeNetworkStatus: String { @@ -127,6 +136,7 @@ struct Pihole { let identifier: String let online: Bool let summary: PiholeAPISummary? + let overTimeData: PiholeOverTimeData? let canBeManaged: Bool? let enabled: Bool? @@ -152,5 +162,13 @@ struct PiholeNetworkOverview { let adsPercentageToday: Double let averageBlocklist: Int + let overTimeData: PiholeNetworkOverTimeData? + let piholes: [String: Pihole] } + +struct PiholeNetworkOverTimeData { + let overview: [Double: (Double, Double)] + let maximumValue: Double + let piholes: [String: [Double: (Double, Double)]] +} diff --git a/PiBar/Manager/Operations/AsyncOperation.swift b/PiBar Shared/Manager/Operations/AsyncOperation.swift similarity index 92% rename from PiBar/Manager/Operations/AsyncOperation.swift rename to PiBar Shared/Manager/Operations/AsyncOperation.swift index 13325c3..953f86b 100644 --- a/PiBar/Manager/Operations/AsyncOperation.swift +++ b/PiBar Shared/Manager/Operations/AsyncOperation.swift @@ -17,7 +17,7 @@ class AsyncOperation: Operation { } override var isAsynchronous: Bool { - return true + true } var state = State.isReady { @@ -32,11 +32,11 @@ class AsyncOperation: Operation { } override var isExecuting: Bool { - return state == .isExecuting + state == .isExecuting } override var isFinished: Bool { - return state == .isFinished + state == .isFinished } override func start() { diff --git a/PiBar/Manager/Operations/ChangePiholeStatusOperation.swift b/PiBar Shared/Manager/Operations/ChangePiholeStatusOperation.swift similarity index 100% rename from PiBar/Manager/Operations/ChangePiholeStatusOperation.swift rename to PiBar Shared/Manager/Operations/ChangePiholeStatusOperation.swift diff --git a/PiBar Shared/Manager/Operations/UpdatePiholeOperation.swift b/PiBar Shared/Manager/Operations/UpdatePiholeOperation.swift new file mode 100644 index 0000000..9f3ebd9 --- /dev/null +++ b/PiBar Shared/Manager/Operations/UpdatePiholeOperation.swift @@ -0,0 +1,76 @@ +// +// UpdatePiholeOperation.swift +// PiBar +// +// Created by Brad Root on 5/26/20. +// Copyright © 2020 Brad Root. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +final class UpdatePiholeOperation: AsyncOperation { + private(set) var pihole: Pihole + + init(_ pihole: Pihole) { + self.pihole = pihole + } + + override func main() { + Log.debug("Updating Pi-hole: \(pihole.identifier)") + + var enabled: Bool? = true + var online = true + var canBeManaged: Bool = false + + var receivedSummary: PiholeAPISummary? + var receivedOverTimeData: PiholeOverTimeData? + + let group = DispatchGroup() + + group.enter() + pihole.api.fetchSummary { summary in + Log.debug("Received Summary for \(self.pihole.identifier)") + receivedSummary = summary + group.leave() + } + + group.enter() + pihole.api.fetchOverTimeData { overTimeData in + Log.debug("Received Over Time Data for \(self.pihole.identifier)") + receivedOverTimeData = overTimeData + group.leave() + } + + group.wait() + + if let summary = receivedSummary { + if summary.status != "enabled" { + enabled = false + } + if !pihole.api.connection.token.isEmpty || !pihole.api.connection.passwordProtected { + canBeManaged = true + } + } else { + enabled = nil + online = false + canBeManaged = false + } + + let updatedPihole = Pihole( + api: pihole.api, + identifier: pihole.api.identifier, + online: online, + summary: receivedSummary, + overTimeData: receivedOverTimeData, + canBeManaged: canBeManaged, + enabled: enabled + ) + + pihole = updatedPihole + + state = .isFinished + } +} diff --git a/PiBar/Manager/PiBarManager.swift b/PiBar Shared/Manager/PiBarManager.swift similarity index 73% rename from PiBar/Manager/PiBarManager.swift rename to PiBar Shared/Manager/PiBarManager.swift index 1972d91..ba876e8 100644 --- a/PiBar/Manager/PiBarManager.swift +++ b/PiBar Shared/Manager/PiBarManager.swift @@ -26,10 +26,10 @@ class PiBarManager: NSObject { private var timer: Timer? private var updateInterval: TimeInterval - private let operationQueue: OperationQueue = OperationQueue() + private let operationQueue = OperationQueue() override init() { - Log.logLevel = .off + Log.logLevel = .debug Log.useEmoji = true operationQueue.maxConcurrentOperationCount = 1 @@ -43,6 +43,7 @@ class PiBarManager: NSObject { adsBlockedToday: 0, adsPercentageToday: 0.0, averageBlocklist: 0, + overTimeData: nil, piholes: [:] ) super.init() @@ -143,6 +144,7 @@ class PiBarManager: NSObject { adsBlockedToday: 0, adsPercentageToday: 0.0, averageBlocklist: 0, + overTimeData: nil, piholes: [:] ) } @@ -161,6 +163,7 @@ class PiBarManager: NSObject { identifier: $0.identifier, online: false, summary: nil, + overTimeData: nil, canBeManaged: nil, enabled: nil ) @@ -203,6 +206,7 @@ class PiBarManager: NSObject { adsBlockedToday: networkBlockedQueries(), adsPercentageToday: networkPercentageBlocked(), averageBlocklist: networkBlocklist(), + overTimeData: generateNetworkOverTimeData(), piholes: piholes ) } @@ -277,4 +281,78 @@ class PiBarManager: NSObject { return false } + + private func generateNetworkOverTimeData() -> PiholeNetworkOverTimeData? { + if piholes.isEmpty { + return nil + } + + var piholesOverTimeData: [String: [Double: (Double, Double)]] = [:] + + for (identifier, pihole) in piholes { + piholesOverTimeData[identifier] = normalizeOverTimeData(pihole) + } + + var overview: [Double: (Double, Double)] = [:] + + var hours: Set = [] + for identifier in piholes.keys { + guard let data = piholesOverTimeData[identifier] else { continue } + hours = hours.union(data.keys) + } + + var maximumHourlyValue: Double = 0.0 + + for hour in hours { + var summedData: (Double, Double) = (0, 0) + for identifier in piholes.keys { + guard let data = piholesOverTimeData[identifier] else { continue } + let queryData: (Double, Double) = data[hour] ?? (0, 0) + summedData = (summedData.0 + queryData.0, summedData.1 + queryData.1) + + let hourlyTotalQueries: Double = summedData.0 + summedData.1 + maximumHourlyValue = hourlyTotalQueries > maximumHourlyValue ? hourlyTotalQueries : maximumHourlyValue + } + overview[hour] = summedData + } + + return PiholeNetworkOverTimeData( + overview: overview, + maximumValue: maximumHourlyValue, + piholes: piholesOverTimeData + ) + } + + private func normalizeOverTimeData(_ pihole: Pihole) -> [Double: (Double, Double)] { + var overTimeData: [Double: (Double, Double)] = [:] + if let domainsOverTime = pihole.overTimeData?.domainsOverTime, + let adsOverTime = pihole.overTimeData?.adsOverTime + { + var hour: Double = 0 + var batchCount: Int = 0 + var summedDomains: Double = 0.0 + var summedAds: Double = 0.0 + + let sorted = domainsOverTime.sorted { $0.key < $1.key } + + for (key, value) in sorted { + if batchCount < 5 { + summedDomains += Double(value) + summedAds += Double(adsOverTime[key] ?? 0) + batchCount += 1 + } else { + overTimeData[hour] = (summedDomains, summedAds) + hour += 1 + summedDomains = 0 + summedAds = 0 + batchCount = 0 + } + } + if !summedDomains.isZero || !summedAds.isZero { + overTimeData[hour] = (summedDomains, summedAds) + } + } + + return overTimeData + } } diff --git a/PiBar/Utilities/Extensions.swift b/PiBar Shared/Utilities/Extensions.swift similarity index 68% rename from PiBar/Utilities/Extensions.swift rename to PiBar Shared/Utilities/Extensions.swift index 673e120..d91340b 100644 --- a/PiBar/Utilities/Extensions.swift +++ b/PiBar Shared/Utilities/Extensions.swift @@ -43,3 +43,30 @@ extension Collection where Element: BinaryInteger { // swiftlint:disable colon func average() -> T { isEmpty ? .zero : T(sum()) / T(count) } } + +public extension Bundle { + // Thanks to: https://stackoverflow.com/a/62383907/2117288 + var appVersionShort: String? { + if let result = infoDictionary?["CFBundleShortVersionString"] as? String { + return result + } else { + return "⚠️" + } + } + + var appVersionLong: String? { + if let result = infoDictionary?["CFBundleVersion"] as? String { + return result + } else { + return "⚠️" + } + } + + var appName: String? { + if let result = infoDictionary?["CFBundleName"] as? String { + return result + } else { + return "⚠️" + } + } +} diff --git a/PiBar/Utilities/Logging.swift b/PiBar Shared/Utilities/Logging.swift similarity index 100% rename from PiBar/Utilities/Logging.swift rename to PiBar Shared/Utilities/Logging.swift diff --git a/PiBar for iOS/AppDelegate.swift b/PiBar for iOS/AppDelegate.swift new file mode 100644 index 0000000..4d43f89 --- /dev/null +++ b/PiBar for iOS/AppDelegate.swift @@ -0,0 +1,45 @@ +// +// AppDelegate.swift +// PiBar for iOS +// +// Created by Brad Root on 5/30/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + ThemeManager.initialize() + return true + } + + // MARK: UISceneSession Lifecycle + + @available(iOS 13.0, *) + func application( + _: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options _: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + @available(iOS 13.0, *) + func application(_: UIApplication, didDiscardSceneSessions _: Set) {} +} + +extension UIApplication { + static func topViewController() -> UIViewController? { + guard var top = shared.keyWindow?.rootViewController else { + return nil + } + while let next = top.presentedViewController { + top = next + } + return top + } +} diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..feb0c43 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "pibar_icon_40px.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "pibar_icon_60px.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "pibar_icon_58px.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "pibar_icon_87px.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "pibar_icon_80px.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "pibar_icon_120px.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "pibar_icon_120px-1.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "pibar_icon_180px.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "pibar_icon_20px.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "pibar_icon_40px-2.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "pibar_icon_29px.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "pibar_icon_58px-1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "pibar_icon_40px-1.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "pibar_icon_80px-1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "pibar_icon_76px.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "pibar_icon_152px.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "pibar_icon_167px.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "pibar_icon_1024px.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_1024px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_1024px.png new file mode 100644 index 0000000..39a62ce Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_1024px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_120px-1.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_120px-1.png new file mode 100644 index 0000000..d23b79c Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_120px-1.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_120px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_120px.png new file mode 100644 index 0000000..d23b79c Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_120px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_152px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_152px.png new file mode 100644 index 0000000..f3ef0c6 Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_152px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_167px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_167px.png new file mode 100644 index 0000000..3451344 Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_167px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_180px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_180px.png new file mode 100644 index 0000000..9a0524a Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_180px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_20px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_20px.png new file mode 100644 index 0000000..5ccad39 Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_20px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_29px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_29px.png new file mode 100644 index 0000000..f9468c5 Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_29px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px-1.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px-1.png new file mode 100644 index 0000000..5cecdaf Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px-1.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px-2.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px-2.png new file mode 100644 index 0000000..5cecdaf Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px-2.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px.png new file mode 100644 index 0000000..5cecdaf Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_40px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_58px-1.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_58px-1.png new file mode 100644 index 0000000..964b75a Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_58px-1.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_58px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_58px.png new file mode 100644 index 0000000..964b75a Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_58px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_60px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_60px.png new file mode 100644 index 0000000..c016ecb Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_60px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_76px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_76px.png new file mode 100644 index 0000000..52e064f Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_76px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_80px-1.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_80px-1.png new file mode 100644 index 0000000..b66628f Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_80px-1.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_80px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_80px.png new file mode 100644 index 0000000..b66628f Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_80px.png differ diff --git a/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_87px.png b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_87px.png new file mode 100644 index 0000000..a54ae62 Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_87px.png differ diff --git a/PiBar/Assets.xcassets/Contents.json b/PiBar for iOS/Assets.xcassets/Contents.json similarity index 100% rename from PiBar/Assets.xcassets/Contents.json rename to PiBar for iOS/Assets.xcassets/Contents.json diff --git a/PiBar for iOS/Assets.xcassets/background.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/background.colorset/Contents.json new file mode 100644 index 0000000..05051dc --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF7", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/blue.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/blue.colorset/Contents.json new file mode 100644 index 0000000..b3167ab --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/blue.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.727", + "green" : "0.679", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.866", + "green" : "0.825", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/chartGridLines.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/chartGridLines.colorset/Contents.json new file mode 100644 index 0000000..9c64f9f --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/chartGridLines.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.800", + "green" : "0.800", + "red" : "0.800" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.200", + "green" : "0.200", + "red" : "0.200" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/containerBackground.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/containerBackground.colorset/Contents.json new file mode 100644 index 0000000..e1797d6 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/containerBackground.colorset/Contents.json @@ -0,0 +1,36 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "extended-gray", + "components" : { + "alpha" : "1.000", + "white" : "0x1B" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/green.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/green.colorset/Contents.json new file mode 100644 index 0000000..7f44f92 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/green.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.023", + "green" : "0.767", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.906", + "red" : "0.059" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/indigo.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/indigo.colorset/Contents.json new file mode 100644 index 0000000..c7d877d --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/indigo.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.478", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.517", + "red" : "0.039" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/network.imageset/Contents.json b/PiBar for iOS/Assets.xcassets/network.imageset/Contents.json new file mode 100644 index 0000000..683acd1 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/network.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "network.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/PiBar for iOS/Assets.xcassets/network.imageset/network.pdf b/PiBar for iOS/Assets.xcassets/network.imageset/network.pdf new file mode 100644 index 0000000..a668f9c Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/network.imageset/network.pdf differ diff --git a/PiBar for iOS/Assets.xcassets/orange.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/orange.colorset/Contents.json new file mode 100644 index 0000000..631c60c --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/orange.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.518", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.551", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/raspberrypi.imageset/Contents.json b/PiBar for iOS/Assets.xcassets/raspberrypi.imageset/Contents.json new file mode 100644 index 0000000..1df37e5 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/raspberrypi.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "raspberrypi.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/PiBar for iOS/Assets.xcassets/raspberrypi.imageset/raspberrypi.pdf b/PiBar for iOS/Assets.xcassets/raspberrypi.imageset/raspberrypi.pdf new file mode 100644 index 0000000..5699ca8 Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/raspberrypi.imageset/raspberrypi.pdf differ diff --git a/PiBar for iOS/Assets.xcassets/red.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/red.colorset/Contents.json new file mode 100644 index 0000000..01cfb65 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/red.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.206", + "green" : "0.092", + "red" : "0.907" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.101", + "green" : "0.045", + "red" : "0.837" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/server.imageset/Contents.json b/PiBar for iOS/Assets.xcassets/server.imageset/Contents.json new file mode 100644 index 0000000..eba8022 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/server.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "server.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/PiBar for iOS/Assets.xcassets/server.imageset/server.pdf b/PiBar for iOS/Assets.xcassets/server.imageset/server.pdf new file mode 100644 index 0000000..dd3729e Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/server.imageset/server.pdf differ diff --git a/PiBar for iOS/Assets.xcassets/settings.imageset/Contents.json b/PiBar for iOS/Assets.xcassets/settings.imageset/Contents.json new file mode 100644 index 0000000..9fd77f5 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/settings.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "Settings Icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/PiBar for iOS/Assets.xcassets/settings.imageset/Settings Icon.pdf b/PiBar for iOS/Assets.xcassets/settings.imageset/Settings Icon.pdf new file mode 100644 index 0000000..1010dd8 Binary files /dev/null and b/PiBar for iOS/Assets.xcassets/settings.imageset/Settings Icon.pdf differ diff --git a/PiBar for iOS/Assets.xcassets/violet.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/violet.colorset/Contents.json new file mode 100644 index 0000000..f87f8ab --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/violet.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.706", + "green" : "0.000", + "red" : "0.738" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.878", + "green" : "0.000", + "red" : "0.918" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Assets.xcassets/yellow.colorset/Contents.json b/PiBar for iOS/Assets.xcassets/yellow.colorset/Contents.json new file mode 100644 index 0000000..b5212f8 --- /dev/null +++ b/PiBar for iOS/Assets.xcassets/yellow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.001", + "green" : "0.843", + "red" : "0.863" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.684", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar for iOS/Base.lproj/LaunchScreen.storyboard b/PiBar for iOS/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/PiBar for iOS/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PiBar for iOS/Info.plist b/PiBar for iOS/Info.plist new file mode 100644 index 0000000..dd98e1f --- /dev/null +++ b/PiBar for iOS/Info.plist @@ -0,0 +1,80 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + PiBar + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 70 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarTintParameters + + UINavigationBar + + Style + UIBarStyleDefault + Translucent + + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/PiBar for iOS/SceneDelegate.swift b/PiBar for iOS/SceneDelegate.swift new file mode 100644 index 0000000..9e0440c --- /dev/null +++ b/PiBar for iOS/SceneDelegate.swift @@ -0,0 +1,49 @@ +// +// SceneDelegate.swift +// PiBar for iOS +// +// Created by Brad Root on 5/30/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +@available(iOS 13.0, *) +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard (scene as? UIWindowScene) != nil else { return } + } + + func sceneDidDisconnect(_: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } +} diff --git a/PiBar for iOS/ThemeManager.swift b/PiBar for iOS/ThemeManager.swift new file mode 100644 index 0000000..763d2be --- /dev/null +++ b/PiBar for iOS/ThemeManager.swift @@ -0,0 +1,53 @@ +// +// ThemeManager.swift +// PiBar for iOS +// +// Created by Brad Root on 8/25/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import Foundation +import UIKit + +class ThemeManager { + static func initialize() { + let colorString = Preferences.standard.interfaceColor + if let foundColor = UIColor(named: colorString) { + applyColor(color: foundColor) + } + } + + static func setColor(color: String) { + if let foundColor = UIColor(named: color) { + Preferences.standard.set(interfaceColor: color) + applyColor(color: foundColor) + } + } + + static func getColor() -> UIColor? { + let colorString = Preferences.standard.interfaceColor + if let foundColor = UIColor(named: colorString) { + return foundColor + } + return nil + } + + static func applyColor(color: UIColor) { + UINavigationBar.appearance().tintColor = color + UIBarButtonItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: color], for: .normal) + UISwitch.appearance().onTintColor = color + UITableViewCell.appearance().tintColor = color + UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = color + PiBarLabel.appearance().configurableTextColor = color + PiBarChartView.appearance().chartColor = color + + for window in UIApplication.shared.windows { + if !window.isKind(of: NSClassFromString("UITextEffectsWindow") ?? NSString.classForCoder()) { + window.subviews.forEach { + $0.removeFromSuperview() + window.addSubview($0) + } + } + } + } +} diff --git a/PiBar for iOS/Views/Add Device/AddDeviceTableViewController.swift b/PiBar for iOS/Views/Add Device/AddDeviceTableViewController.swift new file mode 100644 index 0000000..1d8e191 --- /dev/null +++ b/PiBar for iOS/Views/Add Device/AddDeviceTableViewController.swift @@ -0,0 +1,264 @@ +// +// AddDeviceTableViewController.swift +// PiBar for iOS +// +// Created by Brad Root on 8/18/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +protocol AddDeviceDelegate: AnyObject { + func updatedConnections() +} + +class AddDeviceTableViewController: UITableViewController, UITextFieldDelegate { + @IBOutlet var saveButton: UIBarButtonItem! + @IBAction func saveButtonAction(_: UIBarButtonItem) { + saveConnection() + } + + @IBAction func cancelButtonAction(_: UIBarButtonItem) { + dismiss(animated: true, completion: nil) + } + + @IBOutlet var hostnameTextField: UITextField! + @IBOutlet var portTextField: UITextField! + + @IBOutlet var useSSLStatusLabel: UILabel! + @IBOutlet var useSSLContentView: UIView! + + @IBOutlet var apiTokenTextField: UITextField! + @IBOutlet var adminURLTextField: UITextField! + + @IBOutlet var testingStatusLabel: UILabel! + + @IBOutlet var testButton: UIButton! + @IBAction func testButtonAction(_: UIButton) { + testConnection() + } + + private var useSSLStatus: Bool = false + + private var passwordProtected: Bool = true + + weak var delegate: AddDeviceDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + + testButton.layer.cornerRadius = testButton.frame.height / 2 + + adminURLTextField.delegate = self + apiTokenTextField.delegate = self + hostnameTextField.delegate = self + portTextField.delegate = self + } + + // TextFields + + func textFieldShouldReturn(_: UITextField) -> Bool { + view.endEditing(true) + return false + } + + func textFieldDidEndEditing(_: UITextField) { + updateAdminURLPlaceholder() + saveButton.isEnabled = false + sslFailSafe() + } + + func textFieldDidBeginEditing(_: UITextField) { + saveButton.isEnabled = false + } + + // TableView + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath == IndexPath(row: 2, section: 0) { + // Selected "Use SSL" cell + showUseSSLAlert() + } else if indexPath == IndexPath(row: 1, section: 1) { + // Selected "Where do I find my API token?" + showTokenHelp() + } + tableView.deselectRow(at: indexPath, animated: true) + } +} + +extension AddDeviceTableViewController { + private func updateAdminURLPlaceholder() { + var parsedPort: Int = 0 + if let port = portTextField.text, !port.isEmpty { + parsedPort = Int(port) ?? 80 + } else { + parsedPort = useSSLStatus ? 443 : 80 + } + let adminURLString = PiholeConnectionV2.generateAdminPanelURL( + hostname: ((hostnameTextField.text?.isEmpty)! ? "pi.hole" : hostnameTextField.text) ?? "pi.hole", + port: parsedPort, + useSSL: useSSLStatus + ) + adminURLTextField.placeholder = "\(adminURLString)" + } + + private func showUseSSLAlert() { + let actionSheet = UIAlertController( + title: "Use SSL?", + message: "Select whether this connection should use SSL or not.", + preferredStyle: UIAlertController.Style.actionSheet + ) + let actionOn = UIAlertAction(title: "Yes", style: .default) { _ in + self.useSSLStatus = true + self.useSSLStatusLabel.text = "Yes" + self.portTextField.placeholder = "443" + self.sslFailSafe() + self.updateAdminURLPlaceholder() + } + let actionOff = UIAlertAction(title: "No", style: .default) { _ in + self.useSSLStatus = false + self.useSSLStatusLabel.text = "No" + self.portTextField.placeholder = "80" + self.updateAdminURLPlaceholder() + self.sslFailSafe() + } + let actionCancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + + actionSheet.addAction(actionOn) + actionSheet.addAction(actionOff) + actionSheet.addAction(actionCancel) + + if let popoverController = actionSheet.popoverPresentationController { + popoverController.sourceView = view + popoverController.sourceRect = CGRect( + x: useSSLContentView.bounds.midY, + y: useSSLContentView.bounds.midX, + width: useSSLContentView.bounds.width, + height: useSSLContentView.bounds.height + ) + } + + present(actionSheet, animated: true, completion: nil) + } + + private func showTokenHelp() { + // TODO: + } + + fileprivate func sslFailSafe() { + var port = portTextField.text + if useSSLStatus, port == "80" { + port = "443" + } else if !useSSLStatus, port == "443" { + port = "80" + } + portTextField.text = port + } + + func testConnection() { + Log.debug("Testing Connection...") + + testingStatusLabel.text = "Testing... Please wait..." + + var passwordProtected = false + var apiToken = "" + if let token = apiTokenTextField.text { + if token.isEmpty { + passwordProtected = true + apiToken = token + } + } + + let connection = PiholeConnectionV2( + hostname: ((hostnameTextField.text?.isEmpty)! ? "pi.hole" : hostnameTextField.text) ?? "pi.hole", + port: Int(portTextField.text ?? "80") ?? 80, + useSSL: useSSLStatus, + token: apiToken, + passwordProtected: passwordProtected, + adminPanelURL: "" + ) + + let api = PiholeAPI(connection: connection) + + api.testConnection { status in + switch status { + case .success: + self.testingStatusLabel.text = "Success!" + self.saveButton.isEnabled = true + case .failure: + self.testingStatusLabel.text = "Unable to Connect" + self.saveButton.isEnabled = false + case .failureInvalidToken: + self.testingStatusLabel.text = "Invalid API Token" + self.saveButton.isEnabled = false + } + } + } + + fileprivate func saveConnection() { + let hostname = ((hostnameTextField.text?.isEmpty)! ? "pi.hole" : hostnameTextField.text) ?? "pi.hole" + let port = Int(portTextField.text ?? "80") ?? 80 + var adminPanelURL = adminURLTextField.text ?? "" + var apiToken = "" + if let token = apiTokenTextField.text { + if !token.isEmpty { + passwordProtected = true + apiToken = token + } + } + + if adminPanelURL.isEmpty { + adminPanelURL = PiholeConnectionV2.generateAdminPanelURL( + hostname: hostname, + port: port, + useSSL: useSSLStatus + ) + } + + let connection = PiholeConnectionV2( + hostname: hostname, + port: port, + useSSL: useSSLStatus, + token: apiToken, + passwordProtected: passwordProtected, + adminPanelURL: adminPanelURL + ) + + var piholes = Preferences.standard.piholes + piholes.append(connection) + Preferences.standard.set(piholes: piholes) + delegate?.updatedConnections() + dismiss(animated: true, completion: nil) + } +} + +extension UITextField { + @IBInspectable var doneAccessory: Bool { + get { + self.doneAccessory + } + set(hasDone) { + if hasDone { + addDoneButtonOnKeyboard() + } + } + } + + func addDoneButtonOnKeyboard() { + let doneToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50)) + doneToolbar.barStyle = .default + + let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let done = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(doneButtonAction)) + + let items = [flexSpace, done] + doneToolbar.items = items + doneToolbar.sizeToFit() + + inputAccessoryView = doneToolbar + } + + @objc func doneButtonAction() { + resignFirstResponder() + } +} diff --git a/PiBar for iOS/Views/Base.lproj/Main.storyboard b/PiBar for iOS/Views/Base.lproj/Main.storyboard new file mode 100644 index 0000000..1f1f4e0 --- /dev/null +++ b/PiBar for iOS/Views/Base.lproj/Main.storyboard @@ -0,0 +1,1606 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Polling rate controls how quickly your Pi-holes are queried for new information. Lower poll rates could lead to quicker storage degradation in some cases, so set this according to how your Pi-hole is hosted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The checkmark determines which action will be performed by default when disabling your Pi-holes. This option also applies to Family Mode. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Normalize charts ensures that all charts are drawn using the same Y-axis, determined by the combined chart shown in the Overview panel. This makes it easier to visually see how load is distributed between your Pi-holes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PiBar for iOS/Views/Main View/MainViewController.swift b/PiBar for iOS/Views/Main View/MainViewController.swift new file mode 100644 index 0000000..48cb77a --- /dev/null +++ b/PiBar for iOS/Views/Main View/MainViewController.swift @@ -0,0 +1,129 @@ +// +// MainViewController.swift +// PiBar for iOS +// +// Created by Brad Root on 6/2/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +class MainViewController: UIViewController { + private let manager = PiBarManager() + private var networkOverview: PiholeNetworkOverview? { + didSet { + DispatchQueue.main.async { + self.tableView.reloadData() + self.tableView.layoutSubviews() + } + } + } + + @IBOutlet var networkOverviewView: NetworkOverviewView! + @IBOutlet var tableView: UITableView! + + override func viewDidLoad() { + super.viewDidLoad() + + networkOverviewView.manager = manager + + Preferences.standard.set(piholes: [ + PiholeConnectionV2( + hostname: "pi-hole.local", + port: 80, + useSSL: false, + token: "", + passwordProtected: false, + adminPanelURL: "http://pi-hole.local/admin" + ), + PiholeConnectionV2( + hostname: "rickenbacker.local", + port: 80, + useSSL: false, + token: "b17660b345e1871a06177e27b06404cd10088d3e19f5d4248b8b349e14f127b8", + passwordProtected: true, + adminPanelURL: "http://rickenbacker.local/admin" + ), + ]) + + manager.delegate = self + + manager.loadConnections() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + tableView.contentInset.bottom = networkOverviewView.frame.height - view.safeAreaInsets.bottom + 10 + if UIDevice.current.userInterfaceIdiom == .phone { + // I like how insets look on iPhone, but not iPad + tableView.scrollIndicatorInsets.bottom = networkOverviewView.frame.height - view.safeAreaInsets.bottom + } + } + + override func prepare(for segue: UIStoryboardSegue, sender _: Any?) { + if segue.identifier == "showAddDeviceSegue", + let navController = segue.destination as? UINavigationController, + let view = navController.topViewController as? AddDeviceTableViewController + { + view.delegate = self + } else if segue.identifier == "showPreferences", + let navController = segue.destination as? UINavigationController, + let view = navController.topViewController as? PreferencesTableViewController + { + view.delegate = self + } + } +} + +extension MainViewController: PiBarManagerDelegate { + func updateNetwork(_ network: PiholeNetworkOverview) { + networkOverviewView.networkOverview = network + networkOverview = network + } +} + +extension MainViewController: PreferencesDelegate { + func updatedPreferences() { + manager.setPollingRate(to: Preferences.standard.pollingRate) + } +} + +extension MainViewController: UITableViewDelegate {} + +extension MainViewController: UITableViewDataSource { + func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { + guard let networkOverview = self.networkOverview else { return 0 } + + return networkOverview.piholes.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let piholes = networkOverview?.piholes, + let cell = tableView.dequeueReusableCell(withIdentifier: "piholeCell", for: indexPath) as? PiholeTableViewCell + else { + let cell = tableView.dequeueReusableCell(withIdentifier: "piholeCell", for: indexPath) + return cell + } + + let piholeIdentifiersAlphabetized: [String] = piholes.keys.sorted() + + cell.pihole = piholes[piholeIdentifiersAlphabetized[indexPath.row]] + + if let overTimeData = networkOverview?.overTimeData, + let piholeData = overTimeData.piholes[piholeIdentifiersAlphabetized[indexPath.row]] + { + cell.chartData = (overTimeData.maximumValue, piholeData) + } + + return cell + } +} + +extension MainViewController: AddDeviceDelegate { + func updatedConnections() { + Log.debug("Connections Updated") + manager.loadConnections() + tableView.reloadData() + } +} diff --git a/PiBar for iOS/Views/Main View/NetworkOverviewView.swift b/PiBar for iOS/Views/Main View/NetworkOverviewView.swift new file mode 100644 index 0000000..b2d1f4c --- /dev/null +++ b/PiBar for iOS/Views/Main View/NetworkOverviewView.swift @@ -0,0 +1,147 @@ +// +// NetworkOverviewView.swift +// PiBar for iOS +// +// Created by Brad Root on 6/2/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import Charts +import UIKit + +enum ButtonBehavior { + case enable + case disable + case pending +} + +class NetworkOverviewView: UIView { + weak var manager: PiBarManager? + + var buttonBehavior: ButtonBehavior = .disable { + didSet { + switch buttonBehavior { + case .enable: + disableButton.setTitle("Enable", for: .normal) + disableButton.isEnabled = true + case .disable: + disableButton.setTitle("Disable", for: .normal) + disableButton.isEnabled = true + case .pending: + disableButton.setTitle("Disable", for: .normal) + disableButton.isEnabled = false + } + } + } + + @IBOutlet var totalQueriesLabel: UILabel! + @IBOutlet var blockedQueriesLabel: UILabel! + @IBOutlet var networkStatusLabel: UILabel! + @IBOutlet var avgBlocklistLabel: UILabel! + + @IBOutlet var disableButton: UIButton! + @IBOutlet var viewQueriesButton: UIButton! + + @IBOutlet var chart: PiBarChartView! + + @IBAction func disableButtonAction(_: UIButton) { + if buttonBehavior == .disable { + let seconds = Preferences.standard.defaultDisableDuration + if seconds > 0 { + Log.info("Disabling via Menu for \(String(describing: seconds)) seconds") + manager?.disableNetwork(seconds: seconds) + } else if seconds == 0 { + Log.info("Disabling via Menu permanently") + manager?.disableNetwork() + } else { + showDisableMenu() + } + } else if buttonBehavior == .enable { + manager?.enableNetwork() + } + } + + var networkOverview: PiholeNetworkOverview? { + didSet { + DispatchQueue.main.async { + guard let networkOverview = self.networkOverview else { return } + self.totalQueriesLabel.text = networkOverview.totalQueriesToday.string + self.blockedQueriesLabel.text = networkOverview.adsBlockedToday.string + self.networkStatusLabel.text = networkOverview.networkStatus.rawValue + self.avgBlocklistLabel.text = networkOverview.averageBlocklist.string + + switch networkOverview.networkStatus { + case .enabled: + self.buttonBehavior = .disable + case .disabled: + self.buttonBehavior = .enable + case .partiallyEnabled: + self.buttonBehavior = .disable + case .offline: + self.buttonBehavior = .pending + case .partiallyOffline: + self.buttonBehavior = .disable + case .noneSet: + self.buttonBehavior = .pending + case .initializing: + self.buttonBehavior = .pending + } + + self.updateChart() + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + disableButton.layer.cornerRadius = disableButton.frame.height / 2 + viewQueriesButton.layer.cornerRadius = viewQueriesButton.frame.height / 2 + + let maskLayer = CAShapeLayer() + maskLayer.path = UIBezierPath( + roundedRect: bounds, + byRoundingCorners: [.topLeft, .topRight], + cornerRadii: CGSize(width: 38.5, height: 38.5) + ).cgPath + layer.mask = maskLayer + clipsToBounds = true + } + + func updateChart() { + // Chart Data + guard let dataOverTime = networkOverview?.overTimeData, !dataOverTime.overview.isEmpty else { return } + chart.loadDataOverTime(dataOverTime.overview, maxValue: dataOverTime.maximumValue) + } + + func showDisableMenu() { + let disableActionSheet = UIAlertController(title: "Disable Pi-holes", message: nil, preferredStyle: .actionSheet) + + disableActionSheet.popoverPresentationController?.sourceView = disableButton.superview! + disableActionSheet.popoverPresentationController?.sourceRect = disableButton.frame + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + disableActionSheet.addAction(cancelAction) + + let permAction = UIAlertAction(title: "Permanently", style: .destructive) { _ in + self.manager?.disableNetwork() + } + disableActionSheet.addAction(permAction) + + let disableTimes: [(Int, String)] = [ + (3600, "1 Hour"), + (900, "15 Minutes"), + (300, "5 Minutes"), + (30, "30 Seconds"), + (10, "10 Seconds"), + ] + for time in disableTimes { + let action = UIAlertAction(title: time.1, style: .default) { _ in + self.manager?.disableNetwork(seconds: time.0) + } + disableActionSheet.addAction(action) + } + + UIApplication.topViewController()?.present(disableActionSheet, animated: true, completion: nil) + } +} diff --git a/PiBar for iOS/Views/Main View/PiBarChartView.swift b/PiBar for iOS/Views/Main View/PiBarChartView.swift new file mode 100644 index 0000000..3a8e2f9 --- /dev/null +++ b/PiBar for iOS/Views/Main View/PiBarChartView.swift @@ -0,0 +1,109 @@ +// +// PiBarChartView.swift +// PiBar for iOS +// +// Created by Brad Root on 6/5/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import Charts + +class PiBarChartView: BarChartView { + var color = UIColor(named: "red") ?? .systemRed + + override init(frame: CGRect) { + super.init(frame: frame) + setupChartPreferences() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupChartPreferences() + } + + func setupChartPreferences() { + // Chart setup + chartDescription?.enabled = false + isUserInteractionEnabled = false + legend.enabled = false + minOffset = 0 + + xAxis.drawGridLinesEnabled = true + xAxis.drawAxisLineEnabled = false + xAxis.drawLabelsEnabled = false + xAxis.enabled = true + + xAxis.gridColor = UIColor(named: "chartGridLines") ?? UIColor.darkGray + xAxis.gridLineWidth = 0.5 + + leftAxis.enabled = false + leftAxis.drawGridLinesEnabled = false + leftAxis.axisMinimum = 0 + leftAxis.drawAxisLineEnabled = false + leftAxis.drawLabelsEnabled = false + + rightAxis.enabled = false + } + + func loadDataOverTime(_ dataOverTime: [Double: (Double, Double)], maxValue: Double) { + var yVals: [BarChartDataEntry] = [] + let sorted = dataOverTime.sorted { $0.key < $1.key } + + for (key, value) in sorted { + let entry = BarChartDataEntry(x: key, yValues: [value.0, value.1]) + yVals.append(entry) + } + + if yVals.isEmpty { return } + + xAxis.labelCount = yVals.count + + var set1: BarChartDataSet! + if let set = data?.dataSets.first as? BarChartDataSet { + set1 = set + set1.replaceEntries(yVals) + + if Preferences.standard.normalizeCharts { + leftAxis.axisMaximum = maxValue + } else { + leftAxis.resetCustomAxisMax() + } + + data?.notifyDataChanged() + notifyDataSetChanged() + } else { + set1 = BarChartDataSet(entries: yVals) + set1.label = "Queries Over Time" + set1.colors = [color, .darkGray] + set1.drawValuesEnabled = false + + if Preferences.standard.normalizeCharts { + leftAxis.axisMaximum = maxValue + } else { + leftAxis.resetCustomAxisMax() + } + + let barChartData = BarChartData(dataSet: set1) + barChartData.barWidth = 0.8 + data = barChartData + } + } + + func redrawChart() { + if let set = data?.dataSets.first as? BarChartDataSet { + set.colors = [color, .darkGray] + data?.notifyDataChanged() + notifyDataSetChanged() + } + } + + @objc dynamic var chartColor: UIColor { + get { + color + } + set { + color = newValue + redrawChart() + } + } +} diff --git a/PiBar for iOS/Views/Main View/PiholeTableViewCell.swift b/PiBar for iOS/Views/Main View/PiholeTableViewCell.swift new file mode 100644 index 0000000..d15fb68 --- /dev/null +++ b/PiBar for iOS/Views/Main View/PiholeTableViewCell.swift @@ -0,0 +1,76 @@ +// +// PiholeTableViewCell.swift +// PiBar for iOS +// +// Created by Brad Root on 6/3/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import Charts +import UIKit + +class PiholeTableViewCell: UITableViewCell { + var pihole: Pihole? { + didSet { + DispatchQueue.main.async { + guard let pihole = self.pihole else { return } + self.hostnameLabel.text = pihole.api.connection.hostname + + guard let summary = pihole.summary else { + self.totalQueriesLabel.text = "0" + self.blockedQueriesLabel.text = "0" + self.blocklistLabel.text = "0" + self.currentStatusLabel.text = "Offline" + return + } + self.totalQueriesLabel.text = summary.dnsQueriesToday.string + self.blockedQueriesLabel.text = summary.adsBlockedToday.string + self.blocklistLabel.text = summary.domainsBeingBlocked.string + self.currentStatusLabel.text = pihole.status.rawValue.capitalized + } + } + } + + var chartData: (Double, [Double: (Double, Double)]) = (0, [:]) { + didSet { + DispatchQueue.main.async { + self.updateChart() + } + } + } + + @IBOutlet var hostnameLabel: UILabel! + @IBOutlet var currentStatusLabel: UILabel! + @IBOutlet var totalQueriesLabel: UILabel! + @IBOutlet var blockedQueriesLabel: UILabel! + @IBOutlet var blocklistLabel: UILabel! + + @IBOutlet var containerView: UIView! + + @IBOutlet var chart: PiBarChartView! + + fileprivate func roundCorners() { + let maskLayer = CAShapeLayer() + maskLayer.path = UIBezierPath( + roundedRect: containerView.bounds, + byRoundingCorners: .allCorners, + cornerRadii: CGSize(width: 15, height: 15) + ).cgPath + containerView.layer.mask = maskLayer + containerView.clipsToBounds = true + } + + override func layoutSubviews() { + super.layoutSubviews() + roundCorners() + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + } + + func updateChart() { + if chartData.1.isEmpty { return } + chart.loadDataOverTime(chartData.1, maxValue: chartData.0) + } +} diff --git a/PiBar for iOS/Views/PiBarLabel.swift b/PiBar for iOS/Views/PiBarLabel.swift new file mode 100644 index 0000000..2344a09 --- /dev/null +++ b/PiBar for iOS/Views/PiBarLabel.swift @@ -0,0 +1,20 @@ +// +// PiBarLabel.swift +// PiBar for iOS +// +// Created by Brad Root on 8/25/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +class PiBarLabel: UILabel { + @objc dynamic var configurableTextColor: UIColor { + get { + textColor + } + set { + textColor = newValue + } + } +} diff --git a/PiBar for iOS/Views/Preferences/DisableDurationTableViewController.swift b/PiBar for iOS/Views/Preferences/DisableDurationTableViewController.swift new file mode 100644 index 0000000..cb98228 --- /dev/null +++ b/PiBar for iOS/Views/Preferences/DisableDurationTableViewController.swift @@ -0,0 +1,57 @@ +// +// DisableDurationTableViewController.swift +// PiBar for iOS +// +// Created by Brad Root on 11/2/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +class DisableDurationTableViewController: UITableViewController { + weak var selectedCell: UITableViewCell? + weak var delegate: PreferencesDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // there is undoubtedly a better way to do this, but this works + var indexPath = IndexPath(row: 0, section: 0) + switch Preferences.standard.defaultDisableDuration { + case -1: + indexPath = IndexPath(row: 0, section: 0) + case 0: + indexPath = IndexPath(row: 6, section: 0) + case 10: + indexPath = IndexPath(row: 1, section: 0) + case 30: + indexPath = IndexPath(row: 2, section: 0) + case 300: + indexPath = IndexPath(row: 3, section: 0) + case 900: + indexPath = IndexPath(row: 4, section: 0) + case 3600: + indexPath = IndexPath(row: 5, section: 0) + default: + indexPath = IndexPath(row: 0, section: 0) + } + if let cell = tableView.cellForRow(at: indexPath) { + selectedCell = cell + cell.accessoryType = .checkmark + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + selectedCell?.accessoryType = .none + if let cell = tableView.cellForRow(at: indexPath) { + cell.accessoryType = .checkmark + Preferences.standard.set(defaultDisableDuration: cell.tag) + selectedCell = cell + delegate?.updatedPreferences() + } + } +} diff --git a/PiBar for iOS/Views/Preferences/InterfaceColorTableViewController.swift b/PiBar for iOS/Views/Preferences/InterfaceColorTableViewController.swift new file mode 100644 index 0000000..e11da0e --- /dev/null +++ b/PiBar for iOS/Views/Preferences/InterfaceColorTableViewController.swift @@ -0,0 +1,42 @@ +// +// InterfaceColorTableViewController.swift +// PiBar for iOS +// +// Created by Brad Root on 8/25/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +class InterfaceColorTableViewController: UITableViewController { + override func viewDidLoad() { + super.viewDidLoad() + } + + override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { + switch indexPath.row { + case 0: + set(color: "red") + case 1: + set(color: "orange") + case 2: + set(color: "yellow") + case 3: + set(color: "green") + case 4: + set(color: "blue") + case 5: + set(color: "indigo") + case 6: + set(color: "violet") + default: + set(color: "red") + } + } +} + +private extension InterfaceColorTableViewController { + func set(color: String) { + ThemeManager.setColor(color: color) + } +} diff --git a/PiBar for iOS/Views/Preferences/NormalizeChartsTableViewController.swift b/PiBar for iOS/Views/Preferences/NormalizeChartsTableViewController.swift new file mode 100644 index 0000000..a391f79 --- /dev/null +++ b/PiBar for iOS/Views/Preferences/NormalizeChartsTableViewController.swift @@ -0,0 +1,26 @@ +// +// NormalizeChartsTableViewController.swift +// PiBar for iOS +// +// Created by Brad Root on 10/25/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +class NormalizeChartsTableViewController: UITableViewController { + @IBOutlet var normalizeChartsToggle: UISwitch! + @IBAction func toggleDidChange(_ sender: UISwitch) { + Preferences.standard.set(normalizeCharts: sender.isOn) + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + normalizeChartsToggle.isOn = Preferences.standard.normalizeCharts + } +} diff --git a/PiBar for iOS/Views/Preferences/PollingRateTableViewController.swift b/PiBar for iOS/Views/Preferences/PollingRateTableViewController.swift new file mode 100644 index 0000000..c769500 --- /dev/null +++ b/PiBar for iOS/Views/Preferences/PollingRateTableViewController.swift @@ -0,0 +1,55 @@ +// +// PollingRateTableViewController.swift +// PiBar for iOS +// +// Created by Brad Root on 10/26/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import UIKit + +class PollingRateTableViewController: UITableViewController { + weak var selectedCell: UITableViewCell? + weak var delegate: PreferencesDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // there is undoubtedly a better way to do this, but this works + var indexPath = IndexPath(row: 0, section: 0) + switch Preferences.standard.pollingRate { + case 5: + indexPath = IndexPath(row: 1, section: 0) + case 10: + indexPath = IndexPath(row: 2, section: 0) + case 15: + indexPath = IndexPath(row: 3, section: 0) + case 30: + indexPath = IndexPath(row: 4, section: 0) + case 60: + indexPath = IndexPath(row: 5, section: 0) + case 300: + indexPath = IndexPath(row: 6, section: 0) + default: + indexPath = IndexPath(row: 0, section: 0) + } + if let cell = tableView.cellForRow(at: indexPath) { + selectedCell = cell + cell.accessoryType = .checkmark + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + selectedCell?.accessoryType = .none + if let cell = tableView.cellForRow(at: indexPath) { + cell.accessoryType = .checkmark + Preferences.standard.set(pollingRate: cell.tag) + selectedCell = cell + delegate?.updatedPreferences() + } + } +} diff --git a/PiBar for iOS/Views/Preferences/PreferencesTableViewController.swift b/PiBar for iOS/Views/Preferences/PreferencesTableViewController.swift new file mode 100644 index 0000000..1c2d2b6 --- /dev/null +++ b/PiBar for iOS/Views/Preferences/PreferencesTableViewController.swift @@ -0,0 +1,137 @@ +// +// PreferencesTableViewController.swift +// PiBar for iOS +// +// Created by Brad Root on 8/24/20. +// Copyright © 2020 Brad Root. All rights reserved. +// + +import Foundation +import MessageUI +import UIKit + +protocol PreferencesDelegate: AnyObject { + func updatedPreferences() +} + +class PreferencesTableViewController: UITableViewController { + weak var delegate: PreferencesDelegate? + + @IBOutlet var interfaceColorLabel: UILabel! + @IBOutlet var normalizeChartsLabel: UILabel! + @IBOutlet var pollingRateLabel: UILabel! + @IBOutlet var disableDurationLabel: UILabel! + + @IBAction func doneButtonAction(_: UIBarButtonItem) { + dismiss(animated: true, completion: nil) + } + + override func viewWillAppear(_: Bool) { + interfaceColorLabel.text = Preferences.standard.interfaceColor.capitalized + normalizeChartsLabel.text = Preferences.standard.normalizeCharts ? "On" : "Off" + + let pollingRate = Preferences.standard.pollingRate + switch pollingRate { + case 60: + pollingRateLabel.text = "1 minute" + case 300: + pollingRateLabel.text = "5 minutes" + default: + pollingRateLabel.text = "\(pollingRate) seconds" + } + + let disableDuration = Preferences.standard.defaultDisableDuration + switch disableDuration { + case -1: + disableDurationLabel.text = "Always Ask" + case 0: + disableDurationLabel.text = "Permanently" + case 10: + disableDurationLabel.text = "10 Seconds" + case 30: + disableDurationLabel.text = "30 Seconds" + case 300: + disableDurationLabel.text = "5 Minutes" + case 900: + disableDurationLabel.text = "15 Minutes" + case 3600: + disableDurationLabel.text = "1 Hour" + default: + disableDurationLabel.text = "Always Ask" + } + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath == IndexPath(row: 0, section: 2) { + UIApplication.shared.open(URL(string: "https://github.com/amiantos/pibar")!) + } else if indexPath == IndexPath(row: 1, section: 2) { + UIApplication.shared.open(URL(string: "https://reddit.com/r/pibar")!) + + } else if indexPath == IndexPath(row: 2, section: 2) { + UIApplication.shared.open(URL(string: "https://twitter.com/amiantos")!) + } else if indexPath == IndexPath(row: 3, section: 2) { + sendEmail() + } + tableView.deselectRow(at: indexPath, animated: true) + } + + override func prepare(for segue: UIStoryboardSegue, sender _: Any?) { + if segue.identifier == "showPollingRate", + let view = segue.destination as? PollingRateTableViewController + { + view.delegate = self + } else if segue.identifier == "showDisableDuration", + let view = segue.destination as? DisableDurationTableViewController + { + view.delegate = self + } + } +} + +extension PreferencesTableViewController: PreferencesDelegate { + func updatedPreferences() { + delegate?.updatedPreferences() + } +} + +extension PreferencesTableViewController: MFMailComposeViewControllerDelegate { + func sendEmail() { + if MFMailComposeViewController.canSendMail() { + let mail = MFMailComposeViewController() + mail.mailComposeDelegate = self + mail.setToRecipients(["bradroot@me.com"]) + mail.setSubject("PiBar for iOS Feedback") + mail.setMessageBody( + """ +

System Information:
+ App Version: \(Bundle.main.appVersionShort!) (\(Bundle.main.appVersionLong!))
+ OS: \(UIDevice.current.systemName) \(UIDevice.current.systemVersion)

+

Put your suggestion/question/problem here:

+ """, + isHTML: true + ) + + present(mail, animated: true) + } else { + let alert = UIAlertController( + title: "Unable to Compose", + message: "PiBar was unable to compose an email for you, please email bradroot@me.com if you need help.", + preferredStyle: .alert + ) + if let themeColor = ThemeManager.getColor() { + alert.view.tintColor = themeColor + } + let alertClose = UIAlertAction(title: "Close", style: .cancel, handler: nil) + alert.addAction(alertClose) + present(alert, animated: true) + } + } + + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith _: MFMailComposeResult, error _: Error?) { + controller.dismiss(animated: true) + } +} diff --git a/PiBar/AppDelegate.swift b/PiBar for macOS/AppDelegate.swift similarity index 100% rename from PiBar/AppDelegate.swift rename to PiBar for macOS/AppDelegate.swift diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/Contents.json b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/Contents.json rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_128px.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_128px.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_128px.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_128px.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_128px@2x.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_128px@2x.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_128px@2x.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_128px@2x.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_16px.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_16px.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_16px.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_16px.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_16px@2x.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_16px@2x.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_16px@2x.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_16px@2x.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_256px.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_256px.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_256px.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_256px.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_256px@2x.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_256px@2x.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_256px@2x.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_256px@2x.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_32px.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_32px.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_32px.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_32px.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_32px@2x.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_32px@2x.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_32px@2x.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_32px@2x.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_512px.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_512px.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_512px.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_512px.png diff --git a/PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_512px@2x.png b/PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_512px@2x.png similarity index 100% rename from PiBar/Assets.xcassets/AppIcon.appiconset/pibar_icon_512px@2x.png rename to PiBar for macOS/Assets.xcassets/AppIcon.appiconset/pibar_icon_512px@2x.png diff --git a/PiBar for macOS/Assets.xcassets/Contents.json b/PiBar for macOS/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/PiBar for macOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PiBar/Assets.xcassets/icon.imageset/Contents.json b/PiBar for macOS/Assets.xcassets/icon.imageset/Contents.json similarity index 100% rename from PiBar/Assets.xcassets/icon.imageset/Contents.json rename to PiBar for macOS/Assets.xcassets/icon.imageset/Contents.json diff --git a/PiBar/Assets.xcassets/icon.imageset/pibar_menuIcon_22px.pdf b/PiBar for macOS/Assets.xcassets/icon.imageset/pibar_menuIcon_22px.pdf similarity index 100% rename from PiBar/Assets.xcassets/icon.imageset/pibar_menuIcon_22px.pdf rename to PiBar for macOS/Assets.xcassets/icon.imageset/pibar_menuIcon_22px.pdf diff --git a/PiBar/Assets.xcassets/iconImage.imageset/Contents.json b/PiBar for macOS/Assets.xcassets/iconImage.imageset/Contents.json similarity index 100% rename from PiBar/Assets.xcassets/iconImage.imageset/Contents.json rename to PiBar for macOS/Assets.xcassets/iconImage.imageset/Contents.json diff --git a/PiBar/Assets.xcassets/iconImage.imageset/pibar_icon_128px.png b/PiBar for macOS/Assets.xcassets/iconImage.imageset/pibar_icon_128px.png similarity index 100% rename from PiBar/Assets.xcassets/iconImage.imageset/pibar_icon_128px.png rename to PiBar for macOS/Assets.xcassets/iconImage.imageset/pibar_icon_128px.png diff --git a/PiBar/Assets.xcassets/iconImage.imageset/pibar_icon_32px@2x.png b/PiBar for macOS/Assets.xcassets/iconImage.imageset/pibar_icon_32px@2x.png similarity index 100% rename from PiBar/Assets.xcassets/iconImage.imageset/pibar_icon_32px@2x.png rename to PiBar for macOS/Assets.xcassets/iconImage.imageset/pibar_icon_32px@2x.png diff --git a/PiBar/Database.swift b/PiBar for macOS/Database.swift similarity index 78% rename from PiBar/Database.swift rename to PiBar for macOS/Database.swift index baecf1e..ee203c9 100644 --- a/PiBar/Database.swift +++ b/PiBar for macOS/Database.swift @@ -39,7 +39,7 @@ struct PBDatabase { extension UserDefaults { var hostname: String { - return string(forKey: PBDatabase.Key.hostname) ?? "pi-hole.local" + string(forKey: PBDatabase.Key.hostname) ?? "pi-hole.local" } var port: Int { @@ -48,27 +48,27 @@ extension UserDefaults { } var token: String { - return string(forKey: PBDatabase.Key.token) ?? "" + string(forKey: PBDatabase.Key.token) ?? "" } var showBlocked: Bool { - return bool(forKey: PBDatabase.Key.showBlocked) + bool(forKey: PBDatabase.Key.showBlocked) } var showQueries: Bool { - return bool(forKey: PBDatabase.Key.showQueries) + bool(forKey: PBDatabase.Key.showQueries) } var showPercentage: Bool { - return bool(forKey: PBDatabase.Key.showPercentage) + bool(forKey: PBDatabase.Key.showPercentage) } var showLabels: Bool { - return bool(forKey: PBDatabase.Key.showLabels) + bool(forKey: PBDatabase.Key.showLabels) } var verboseLabels: Bool { - return bool(forKey: PBDatabase.Key.verboseLabels) + bool(forKey: PBDatabase.Key.verboseLabels) } } diff --git a/PiBar/Info.plist b/PiBar for macOS/Info.plist similarity index 98% rename from PiBar/Info.plist rename to PiBar for macOS/Info.plist index 48970f8..30d9963 100644 --- a/PiBar/Info.plist +++ b/PiBar for macOS/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 669 + 674 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/PiBar/PiBar.entitlements b/PiBar for macOS/PiBar.entitlements similarity index 100% rename from PiBar/PiBar.entitlements rename to PiBar for macOS/PiBar.entitlements diff --git a/PiBar/Views/About/AboutViewController.swift b/PiBar for macOS/Views/About/AboutViewController.swift similarity index 100% rename from PiBar/Views/About/AboutViewController.swift rename to PiBar for macOS/Views/About/AboutViewController.swift diff --git a/PiBar/Views/About/AboutWindowController.swift b/PiBar for macOS/Views/About/AboutWindowController.swift similarity index 100% rename from PiBar/Views/About/AboutWindowController.swift rename to PiBar for macOS/Views/About/AboutWindowController.swift diff --git a/PiBar/Views/Base.lproj/Main.storyboard b/PiBar for macOS/Views/Base.lproj/Main.storyboard similarity index 98% rename from PiBar/Views/Base.lproj/Main.storyboard rename to PiBar for macOS/Views/Base.lproj/Main.storyboard index ca5e99c..15b638f 100644 --- a/PiBar/Views/Base.lproj/Main.storyboard +++ b/PiBar for macOS/Views/Base.lproj/Main.storyboard @@ -9,7 +9,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -75,7 +75,7 @@ - + PiBar © 2020 Brad Root Pi-hole® is a registered trademark of Pi-hole LLC. @@ -98,7 +98,7 @@ of Pi-hole LLC. - + @@ -143,13 +143,13 @@ of Pi-hole LLC. - + - + @@ -157,19 +157,19 @@ of Pi-hole LLC. - + - + - + - + - + @@ -557,7 +557,7 @@ of Pi-hole LLC. - + diff --git a/PiBar/Views/Main Menu/MainMenu.xib b/PiBar for macOS/Views/Main Menu/MainMenu.xib similarity index 100% rename from PiBar/Views/Main Menu/MainMenu.xib rename to PiBar for macOS/Views/Main Menu/MainMenu.xib diff --git a/PiBar/Views/Main Menu/MainMenuController.swift b/PiBar for macOS/Views/Main Menu/MainMenuController.swift similarity index 99% rename from PiBar/Views/Main Menu/MainMenuController.swift rename to PiBar for macOS/Views/Main Menu/MainMenuController.swift index 9b1cad7..26f45ed 100644 --- a/PiBar/Views/Main Menu/MainMenuController.swift +++ b/PiBar for macOS/Views/Main Menu/MainMenuController.swift @@ -15,7 +15,7 @@ import HotKey class MainMenuController: NSObject, NSMenuDelegate, PreferencesDelegate, PiBarManagerDelegate { private let toggleHotKey = HotKey(key: .p, modifiers: [.command, .option, .shift]) - private let manager: PiBarManager = PiBarManager() + private let manager = PiBarManager() private var networkOverview: PiholeNetworkOverview? @@ -165,7 +165,8 @@ class MainMenuController: NSObject, NSMenuDelegate, PreferencesDelegate, PiBarMa private func launchWebAdmin(for identifier: String) { guard let pihole = networkOverview?.piholes[identifier], - let adminURL = URL(string: pihole.api.connection.adminPanelURL) else { + let adminURL = URL(string: pihole.api.connection.adminPanelURL) + else { Log.debug("Could not find Pi-hole with identifier \(identifier)") return } diff --git a/PiBar/Views/Preferences/PiholeSettingsViewController.swift b/PiBar for macOS/Views/Preferences/PiholeSettingsViewController.swift similarity index 100% rename from PiBar/Views/Preferences/PiholeSettingsViewController.swift rename to PiBar for macOS/Views/Preferences/PiholeSettingsViewController.swift diff --git a/PiBar/Views/Preferences/PreferencesViewController.swift b/PiBar for macOS/Views/Preferences/PreferencesViewController.swift similarity index 98% rename from PiBar/Views/Preferences/PreferencesViewController.swift rename to PiBar for macOS/Views/Preferences/PreferencesViewController.swift index 86df243..e9a5b41 100644 --- a/PiBar/Views/Preferences/PreferencesViewController.swift +++ b/PiBar for macOS/Views/Preferences/PreferencesViewController.swift @@ -203,7 +203,7 @@ extension PreferencesViewController: NSTableViewDataSource { extension PreferencesViewController: NSTableViewDelegate { func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { var text: String = "" - var cellIdentifier: NSUserInterfaceItemIdentifier = NSUserInterfaceItemIdentifier(rawValue: "") + var cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "") let pihole = Preferences.standard.piholes[row] if tableColumn == tableView.tableColumns[0] { diff --git a/PiBar/Views/Preferences/PreferencesWindowController.swift b/PiBar for macOS/Views/Preferences/PreferencesWindowController.swift similarity index 100% rename from PiBar/Views/Preferences/PreferencesWindowController.swift rename to PiBar for macOS/Views/Preferences/PreferencesWindowController.swift diff --git a/PiBar.xcodeproj/project.pbxproj b/PiBar.xcodeproj/project.pbxproj index 942ea54..ac8967f 100644 --- a/PiBar.xcodeproj/project.pbxproj +++ b/PiBar.xcodeproj/project.pbxproj @@ -7,7 +7,21 @@ objects = { /* Begin PBXBuildFile section */ - 32954590BDD997C520227DB6 /* Pods_PiBar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04F07DFA596CEEEADF9A90A0 /* Pods_PiBar.framework */; }; + 322617A9F42112CA6678F716 /* Pods_PiBar_for_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 974EF8C581C413D7313D458F /* Pods_PiBar_for_iOS.framework */; }; + 440DE4642482D37D00C55354 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440DE4632482D37D00C55354 /* AppDelegate.swift */; }; + 440DE4662482D37D00C55354 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440DE4652482D37D00C55354 /* SceneDelegate.swift */; }; + 440DE46D2482D37D00C55354 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 440DE46B2482D37D00C55354 /* Main.storyboard */; }; + 440DE46F2482D37D00C55354 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 440DE46E2482D37D00C55354 /* Assets.xcassets */; }; + 440DE4722482D37D00C55354 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 440DE4702482D37D00C55354 /* LaunchScreen.storyboard */; }; + 440DE4782482D58B00C55354 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442F6F862471E8DF008F1101 /* Extensions.swift */; }; + 440DE4792482D58D00C55354 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4415D73524747331009E0B31 /* Logging.swift */; }; + 440DE47A2482D59000C55354 /* Structs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4415D73324731FE0009E0B31 /* Structs.swift */; }; + 440DE47B2482D59300C55354 /* PiholeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449395ED2471C47F00FA0C34 /* PiholeAPI.swift */; }; + 440DE47C2482D59500C55354 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442F6F88247212D1008F1101 /* Preferences.swift */; }; + 440DE47D2482D59900C55354 /* PiBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FFB091247627B100DCEDEC /* PiBarManager.swift */; }; + 440DE47E2482D59C00C55354 /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 441E78AD247E3AF300FBC7A0 /* AsyncOperation.swift */; }; + 440DE47F2482D59F00C55354 /* UpdatePiholeOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 441E78AF247E3BC500FBC7A0 /* UpdatePiholeOperation.swift */; }; + 440DE4802482D5A100C55354 /* ChangePiholeStatusOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 441E78B3247E3F9400FBC7A0 /* ChangePiholeStatusOperation.swift */; }; 4415D73424731FE0009E0B31 /* Structs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4415D73324731FE0009E0B31 /* Structs.swift */; }; 4415D73624747331009E0B31 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4415D73524747331009E0B31 /* Logging.swift */; }; 441E78A9247E21DE00FBC7A0 /* LaunchAtLogin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 441E78A8247E21DE00FBC7A0 /* LaunchAtLogin.framework */; }; @@ -19,8 +33,15 @@ 442F6F872471E8DF008F1101 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442F6F862471E8DF008F1101 /* Extensions.swift */; }; 442F6F89247212D2008F1101 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442F6F88247212D1008F1101 /* Preferences.swift */; }; 442F6F8B24721523008F1101 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442F6F8A24721523008F1101 /* PreferencesViewController.swift */; }; + 44306C9C25466124001EDCD2 /* NormalizeChartsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44306C9B25466124001EDCD2 /* NormalizeChartsTableViewController.swift */; }; + 44306CAB2547C159001EDCD2 /* PollingRateTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44306CAA2547C159001EDCD2 /* PollingRateTableViewController.swift */; }; 4438184E247DE31A009D9B3F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4438184D247DE31A009D9B3F /* AboutViewController.swift */; }; 44381850247DE41B009D9B3F /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4438184F247DE41B009D9B3F /* AboutWindowController.swift */; }; + 444821DE248AE1C000665395 /* PiBarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444821DD248AE1C000665395 /* PiBarChartView.swift */; }; + 448ADF5A25510602008AC333 /* DisableDurationTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 448ADF5925510602008AC333 /* DisableDurationTableViewController.swift */; }; + 448C6F1C248746A2004C30BE /* NetworkOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 448C6F1B248746A2004C30BE /* NetworkOverviewView.swift */; }; + 448C6F1E24874AF5004C30BE /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 448C6F1D24874AF5004C30BE /* MainViewController.swift */; }; + 448C6F202488286E004C30BE /* PiholeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 448C6F1F2488286E004C30BE /* PiholeTableViewCell.swift */; }; 449395D62471ABD600FA0C34 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449395D52471ABD600FA0C34 /* AppDelegate.swift */; }; 449395DA2471ABD700FA0C34 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 449395D92471ABD700FA0C34 /* Assets.xcassets */; }; 449395DD2471ABD700FA0C34 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 449395DB2471ABD700FA0C34 /* Main.storyboard */; }; @@ -28,8 +49,14 @@ 449395E82471AC9D00FA0C34 /* MainMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449395E72471AC9D00FA0C34 /* MainMenuController.swift */; }; 449395EC2471B1EB00FA0C34 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449395EB2471B1EB00FA0C34 /* PreferencesWindowController.swift */; }; 449395EE2471C47F00FA0C34 /* PiholeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449395ED2471C47F00FA0C34 /* PiholeAPI.swift */; }; + 44A69B2724ECE2D200B49EB2 /* AddDeviceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A69B2624ECE2D200B49EB2 /* AddDeviceTableViewController.swift */; }; 44B6DAF2247C70F500D364EC /* PiholeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B6DAF1247C70F500D364EC /* PiholeSettingsViewController.swift */; }; + 44CD689424F5E3ED00115820 /* InterfaceColorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44CD689324F5E3ED00115820 /* InterfaceColorTableViewController.swift */; }; + 44CD689624F5E96100115820 /* PiBarLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44CD689524F5E96100115820 /* PiBarLabel.swift */; }; + 44CD689824F5ED2B00115820 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44CD689724F5ED2B00115820 /* ThemeManager.swift */; }; + 44CF8F9824F4B538005A21B6 /* PreferencesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44CF8F9724F4B538005A21B6 /* PreferencesTableViewController.swift */; }; 44FFB092247627B100DCEDEC /* PiBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FFB091247627B100DCEDEC /* PiBarManager.swift */; }; + D8E2BD45380E2681F4F7BF99 /* Pods_PiBar_for_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB442844C550DE69A8DE1422 /* Pods_PiBar_for_macOS.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -47,8 +74,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 04F07DFA596CEEEADF9A90A0 /* Pods_PiBar.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PiBar.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 08A4966FFD2196F42B1CA80F /* Pods-PiBar.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PiBar.release.xcconfig"; path = "Target Support Files/Pods-PiBar/Pods-PiBar.release.xcconfig"; sourceTree = ""; }; + 440DE4612482D37C00C55354 /* PiBar for iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PiBar for iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 440DE4632482D37D00C55354 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 440DE4652482D37D00C55354 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 440DE46C2482D37D00C55354 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 440DE46E2482D37D00C55354 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 440DE4712482D37D00C55354 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 440DE4732482D37D00C55354 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4415D73324731FE0009E0B31 /* Structs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Structs.swift; sourceTree = ""; }; 4415D73524747331009E0B31 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; 441E78A8247E21DE00FBC7A0 /* LaunchAtLogin.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LaunchAtLogin.framework; path = Carthage/Build/Mac/LaunchAtLogin.framework; sourceTree = ""; }; @@ -59,9 +92,16 @@ 442F6F862471E8DF008F1101 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 442F6F88247212D1008F1101 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 442F6F8A24721523008F1101 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; + 44306C9B25466124001EDCD2 /* NormalizeChartsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NormalizeChartsTableViewController.swift; sourceTree = ""; }; + 44306CAA2547C159001EDCD2 /* PollingRateTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollingRateTableViewController.swift; sourceTree = ""; }; 4438184D247DE31A009D9B3F /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 4438184F247DE41B009D9B3F /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; - 449395D22471ABD600FA0C34 /* PiBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PiBar.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 444821DD248AE1C000665395 /* PiBarChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiBarChartView.swift; sourceTree = ""; }; + 448ADF5925510602008AC333 /* DisableDurationTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableDurationTableViewController.swift; sourceTree = ""; }; + 448C6F1B248746A2004C30BE /* NetworkOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkOverviewView.swift; sourceTree = ""; }; + 448C6F1D24874AF5004C30BE /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + 448C6F1F2488286E004C30BE /* PiholeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiholeTableViewCell.swift; sourceTree = ""; }; + 449395D22471ABD600FA0C34 /* PiBar for macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PiBar for macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 449395D52471ABD600FA0C34 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 449395D92471ABD700FA0C34 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 449395DC2471ABD700FA0C34 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -71,18 +111,37 @@ 449395E72471AC9D00FA0C34 /* MainMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenuController.swift; sourceTree = ""; }; 449395EB2471B1EB00FA0C34 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 449395ED2471C47F00FA0C34 /* PiholeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiholeAPI.swift; sourceTree = ""; }; + 44A69B2624ECE2D200B49EB2 /* AddDeviceTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddDeviceTableViewController.swift; sourceTree = ""; }; 44B6DAF1247C70F500D364EC /* PiholeSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiholeSettingsViewController.swift; sourceTree = ""; }; + 44CD689324F5E3ED00115820 /* InterfaceColorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceColorTableViewController.swift; sourceTree = ""; }; + 44CD689524F5E96100115820 /* PiBarLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiBarLabel.swift; sourceTree = ""; }; + 44CD689724F5ED2B00115820 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; + 44CF8F9724F4B538005A21B6 /* PreferencesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesTableViewController.swift; sourceTree = ""; }; 44FFB091247627B100DCEDEC /* PiBarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiBarManager.swift; sourceTree = ""; }; + 5D3D6C1A46DF2617F8C7B902 /* Pods-PiBar for iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PiBar for iOS.release.xcconfig"; path = "Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.release.xcconfig"; sourceTree = ""; }; + 61E78EDFF852EC8679380516 /* Pods-PiBar for iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PiBar for iOS.debug.xcconfig"; path = "Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.debug.xcconfig"; sourceTree = ""; }; + 974EF8C581C413D7313D458F /* Pods_PiBar_for_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PiBar_for_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A8CE572444810D388817476D /* Pods-PiBar.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PiBar.debug.xcconfig"; path = "Target Support Files/Pods-PiBar/Pods-PiBar.debug.xcconfig"; sourceTree = ""; }; + AB442844C550DE69A8DE1422 /* Pods_PiBar_for_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PiBar_for_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE170268940B6C31FF721B6 /* Pods-PiBar for macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PiBar for macOS.release.xcconfig"; path = "Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.release.xcconfig"; sourceTree = ""; }; + FADC58092194CBF2E4F9D976 /* Pods-PiBar for macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PiBar for macOS.debug.xcconfig"; path = "Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 440DE45E2482D37C00C55354 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 322617A9F42112CA6678F716 /* Pods_PiBar_for_iOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 449395CF2471ABD600FA0C34 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 441E78A9247E21DE00FBC7A0 /* LaunchAtLogin.framework in Frameworks */, - 32954590BDD997C520227DB6 /* Pods_PiBar.framework in Frameworks */, + D8E2BD45380E2681F4F7BF99 /* Pods_PiBar_for_macOS.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -94,10 +153,38 @@ children = ( A8CE572444810D388817476D /* Pods-PiBar.debug.xcconfig */, 08A4966FFD2196F42B1CA80F /* Pods-PiBar.release.xcconfig */, + FADC58092194CBF2E4F9D976 /* Pods-PiBar for macOS.debug.xcconfig */, + DCE170268940B6C31FF721B6 /* Pods-PiBar for macOS.release.xcconfig */, + 61E78EDFF852EC8679380516 /* Pods-PiBar for iOS.debug.xcconfig */, + 5D3D6C1A46DF2617F8C7B902 /* Pods-PiBar for iOS.release.xcconfig */, ); path = Pods; sourceTree = ""; }; + 440DE4622482D37D00C55354 /* PiBar for iOS */ = { + isa = PBXGroup; + children = ( + 440DE4632482D37D00C55354 /* AppDelegate.swift */, + 440DE4652482D37D00C55354 /* SceneDelegate.swift */, + 440DE46E2482D37D00C55354 /* Assets.xcassets */, + 440DE4702482D37D00C55354 /* LaunchScreen.storyboard */, + 440DE4732482D37D00C55354 /* Info.plist */, + 444821E0248AFB2000665395 /* Views */, + 44CD689724F5ED2B00115820 /* ThemeManager.swift */, + ); + path = "PiBar for iOS"; + sourceTree = ""; + }; + 440DE4772482D54E00C55354 /* PiBar Shared */ = { + isa = PBXGroup; + children = ( + 441E78B2247E3ED700FBC7A0 /* Manager */, + 4415D73224731E2F009E0B31 /* Data Sources */, + 44AC125F2476F1F300851ED9 /* Utilities */, + ); + path = "PiBar Shared"; + sourceTree = ""; + }; 4415D73224731E2F009E0B31 /* Data Sources */ = { isa = PBXGroup; children = ( @@ -157,13 +244,38 @@ path = About; sourceTree = ""; }; + 444821E0248AFB2000665395 /* Views */ = { + isa = PBXGroup; + children = ( + 440DE46B2482D37D00C55354 /* Main.storyboard */, + 444821E1248AFB3A00665395 /* Main View */, + 44A69B2824ECE2DD00B49EB2 /* Add Device */, + 44CF8F9624F4B4FE005A21B6 /* Preferences */, + 44CD689524F5E96100115820 /* PiBarLabel.swift */, + ); + path = Views; + sourceTree = ""; + }; + 444821E1248AFB3A00665395 /* Main View */ = { + isa = PBXGroup; + children = ( + 448C6F1D24874AF5004C30BE /* MainViewController.swift */, + 448C6F1B248746A2004C30BE /* NetworkOverviewView.swift */, + 448C6F1F2488286E004C30BE /* PiholeTableViewCell.swift */, + 444821DD248AE1C000665395 /* PiBarChartView.swift */, + ); + path = "Main View"; + sourceTree = ""; + }; 449395C92471ABD600FA0C34 = { isa = PBXGroup; children = ( 442F6F842471D334008F1101 /* README.md */, - 449395D42471ABD600FA0C34 /* PiBar */, - 449395D32471ABD600FA0C34 /* Products */, + 440DE4772482D54E00C55354 /* PiBar Shared */, + 449395D42471ABD600FA0C34 /* PiBar for macOS */, + 440DE4622482D37D00C55354 /* PiBar for iOS */, 214831E348424C32082556DF /* Pods */, + 449395D32471ABD600FA0C34 /* Products */, 913B94517DDE1EF8E0C98FB2 /* Frameworks */, ); sourceTree = ""; @@ -171,24 +283,22 @@ 449395D32471ABD600FA0C34 /* Products */ = { isa = PBXGroup; children = ( - 449395D22471ABD600FA0C34 /* PiBar.app */, + 449395D22471ABD600FA0C34 /* PiBar for macOS.app */, + 440DE4612482D37C00C55354 /* PiBar for iOS.app */, ); name = Products; sourceTree = ""; }; - 449395D42471ABD600FA0C34 /* PiBar */ = { + 449395D42471ABD600FA0C34 /* PiBar for macOS */ = { isa = PBXGroup; children = ( 449395DE2471ABD700FA0C34 /* Info.plist */, 449395DF2471ABD700FA0C34 /* PiBar.entitlements */, 449395D92471ABD700FA0C34 /* Assets.xcassets */, 449395D52471ABD600FA0C34 /* AppDelegate.swift */, - 441E78B2247E3ED700FBC7A0 /* Manager */, - 4415D73224731E2F009E0B31 /* Data Sources */, 441E78A6247DE6CB00FBC7A0 /* Views */, - 44AC125F2476F1F300851ED9 /* Utilities */, ); - path = PiBar; + path = "PiBar for macOS"; sourceTree = ""; }; 449395E92471B10500FA0C34 /* Main Menu */ = { @@ -200,6 +310,14 @@ path = "Main Menu"; sourceTree = ""; }; + 44A69B2824ECE2DD00B49EB2 /* Add Device */ = { + isa = PBXGroup; + children = ( + 44A69B2624ECE2D200B49EB2 /* AddDeviceTableViewController.swift */, + ); + path = "Add Device"; + sourceTree = ""; + }; 44AC125F2476F1F300851ED9 /* Utilities */ = { isa = PBXGroup; children = ( @@ -209,11 +327,24 @@ path = Utilities; sourceTree = ""; }; + 44CF8F9624F4B4FE005A21B6 /* Preferences */ = { + isa = PBXGroup; + children = ( + 44CF8F9724F4B538005A21B6 /* PreferencesTableViewController.swift */, + 44CD689324F5E3ED00115820 /* InterfaceColorTableViewController.swift */, + 44306C9B25466124001EDCD2 /* NormalizeChartsTableViewController.swift */, + 44306CAA2547C159001EDCD2 /* PollingRateTableViewController.swift */, + 448ADF5925510602008AC333 /* DisableDurationTableViewController.swift */, + ); + path = Preferences; + sourceTree = ""; + }; 913B94517DDE1EF8E0C98FB2 /* Frameworks */ = { isa = PBXGroup; children = ( 441E78A8247E21DE00FBC7A0 /* LaunchAtLogin.framework */, - 04F07DFA596CEEEADF9A90A0 /* Pods_PiBar.framework */, + AB442844C550DE69A8DE1422 /* Pods_PiBar_for_macOS.framework */, + 974EF8C581C413D7313D458F /* Pods_PiBar_for_iOS.framework */, ); name = Frameworks; sourceTree = ""; @@ -221,12 +352,33 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 449395D12471ABD600FA0C34 /* PiBar */ = { + 440DE4602482D37C00C55354 /* PiBar for iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = 449395E22471ABD700FA0C34 /* Build configuration list for PBXNativeTarget "PiBar" */; + buildConfigurationList = 440DE4742482D37D00C55354 /* Build configuration list for PBXNativeTarget "PiBar for iOS" */; + buildPhases = ( + B5518C6557009A6933ED466D /* [CP] Check Pods Manifest.lock */, + 440DE45D2482D37C00C55354 /* Sources */, + 440DE45E2482D37C00C55354 /* Frameworks */, + 440DE45F2482D37C00C55354 /* Resources */, + E0873B6BD66E09A2764D8708 /* [CP] Embed Pods Frameworks */, + 444821DF248AE87200665395 /* SwiftLint */, + 441D68932546382600ED81AB /* Increment Build Number */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "PiBar for iOS"; + productName = "PiBar for iOS"; + productReference = 440DE4612482D37C00C55354 /* PiBar for iOS.app */; + productType = "com.apple.product-type.application"; + }; + 449395D12471ABD600FA0C34 /* PiBar for macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 449395E22471ABD700FA0C34 /* Build configuration list for PBXNativeTarget "PiBar for macOS" */; buildPhases = ( - 44B6DAF3247CC9A000D364EC /* Increment Build Number */, 556BA81D41547EB1F3EC6986 /* [CP] Check Pods Manifest.lock */, + 44B6DAF3247CC9A000D364EC /* Increment Build Number */, 449395CE2471ABD600FA0C34 /* Sources */, 449395CF2471ABD600FA0C34 /* Frameworks */, 449395D02471ABD600FA0C34 /* Resources */, @@ -234,16 +386,16 @@ CF30F66DAC81C5868939E5C0 /* [CP] Embed Pods Frameworks */, 441E78AB247E21DE00FBC7A0 /* Embed Frameworks */, 441E78A7247E203700FBC7A0 /* LaunchAtLogin */, - 441E78AC247E221800FBC7A0 /* ShellScript */, - 441E78B5247ECBD700FBC7A0 /* ShellScript */, + 441E78AC247E221800FBC7A0 /* Carthage */, + 441E78B5247ECBD700FBC7A0 /* Sign LaunchAtLogin */, ); buildRules = ( ); dependencies = ( ); - name = PiBar; + name = "PiBar for macOS"; productName = PiBar; - productReference = 449395D22471ABD600FA0C34 /* PiBar.app */; + productReference = 449395D22471ABD600FA0C34 /* PiBar for macOS.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -252,10 +404,13 @@ 449395CA2471ABD600FA0C34 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1140; - LastUpgradeCheck = 1140; + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1210; ORGANIZATIONNAME = "Brad Root"; TargetAttributes = { + 440DE4602482D37C00C55354 = { + CreatedOnToolsVersion = 11.5; + }; 449395D12471ABD600FA0C34 = { CreatedOnToolsVersion = 11.4.1; }; @@ -274,12 +429,23 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 449395D12471ABD600FA0C34 /* PiBar */, + 449395D12471ABD600FA0C34 /* PiBar for macOS */, + 440DE4602482D37C00C55354 /* PiBar for iOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 440DE45F2482D37C00C55354 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 440DE4722482D37D00C55354 /* LaunchScreen.storyboard in Resources */, + 440DE46F2482D37D00C55354 /* Assets.xcassets in Resources */, + 440DE46D2482D37D00C55354 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 449395D02471ABD600FA0C34 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -312,6 +478,24 @@ shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; }; + 441D68932546382600ED81AB /* Increment Build Number */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Increment Build Number"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n"; + }; 441E78A7247E203700FBC7A0 /* LaunchAtLogin */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -330,7 +514,7 @@ shellPath = /bin/sh; shellScript = "\"${PROJECT_DIR}/Carthage/Build/Mac/LaunchAtLogin.framework/Resources/copy-helper.sh\"\n"; }; - 441E78AC247E221800FBC7A0 /* ShellScript */ = { + 441E78AC247E221800FBC7A0 /* Carthage */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -340,6 +524,7 @@ inputPaths = ( "$(SRCROOT)/Carthage/Build/Mac/LaunchAtLogin.framework", ); + name = Carthage; outputFileListPaths = ( ); outputPaths = ( @@ -348,7 +533,7 @@ shellPath = /bin/sh; shellScript = "/usr/local/bin/carthage copy-frameworks\n"; }; - 441E78B5247ECBD700FBC7A0 /* ShellScript */ = { + 441E78B5247ECBD700FBC7A0 /* Sign LaunchAtLogin */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -357,6 +542,7 @@ ); inputPaths = ( ); + name = "Sign LaunchAtLogin"; outputFileListPaths = ( ); outputPaths = ( @@ -365,6 +551,24 @@ shellPath = /bin/sh; shellScript = "#!/bin/bash\n\nframework_path=\"$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH/LaunchAtLogin.framework\"\norigin_helper_path=\"$framework_path/Resources/LaunchAtLoginHelper.app\"\nhelper_dir=\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Library/LoginItems\"\nhelper_path=\"$helper_dir/LaunchAtLoginHelper.app\"\n\nrm -rf \"$helper_path\"\nmkdir -p \"$helper_dir\"\ncp -rf \"$origin_helper_path\" \"$helper_dir/\"\n\ndefaults write \"$helper_path/Contents/Info\" CFBundleIdentifier -string \"$PRODUCT_BUNDLE_IDENTIFIER-LaunchAtLoginHelper\"\n\nif [[ -z ${CODE_SIGN_ENTITLEMENTS} ]];\nthen\n codesign --verbose --force -o runtime --deep --sign=\"$EXPANDED_CODE_SIGN_IDENTITY_NAME\" \"$helper_path\"\nelse\n codesign --verbose --force --entitlements=\"$CODE_SIGN_ENTITLEMENTS\" -o runtime --deep --sign=\"$EXPANDED_CODE_SIGN_IDENTITY_NAME\" \"$helper_path\"\nfi\n\nif [[ $CONFIGURATION == \"Release\" ]]; then\n rm -rf \"$origin_helper_path\"\n rm \"$(dirname \"$origin_helper_path\")/copy-helper.sh\"\nfi\n\ncodesign --verbose --force -o runtime --sign \"$EXPANDED_CODE_SIGN_IDENTITY_NAME\" \"$framework_path/Versions/A\"\n"; }; + 444821DF248AE87200665395 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; + }; 44B6DAF3247CC9A000D364EC /* Increment Build Number */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -398,7 +602,29 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PiBar-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-PiBar for macOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B5518C6557009A6933ED466D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PiBar for iOS-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -411,20 +637,67 @@ files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E0873B6BD66E09A2764D8708 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 440DE45D2482D37C00C55354 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 44CF8F9824F4B538005A21B6 /* PreferencesTableViewController.swift in Sources */, + 448C6F1C248746A2004C30BE /* NetworkOverviewView.swift in Sources */, + 44A69B2724ECE2D200B49EB2 /* AddDeviceTableViewController.swift in Sources */, + 448C6F1E24874AF5004C30BE /* MainViewController.swift in Sources */, + 444821DE248AE1C000665395 /* PiBarChartView.swift in Sources */, + 44306CAB2547C159001EDCD2 /* PollingRateTableViewController.swift in Sources */, + 44CD689424F5E3ED00115820 /* InterfaceColorTableViewController.swift in Sources */, + 440DE4782482D58B00C55354 /* Extensions.swift in Sources */, + 448ADF5A25510602008AC333 /* DisableDurationTableViewController.swift in Sources */, + 44CD689824F5ED2B00115820 /* ThemeManager.swift in Sources */, + 44306C9C25466124001EDCD2 /* NormalizeChartsTableViewController.swift in Sources */, + 440DE47E2482D59C00C55354 /* AsyncOperation.swift in Sources */, + 440DE4792482D58D00C55354 /* Logging.swift in Sources */, + 440DE47A2482D59000C55354 /* Structs.swift in Sources */, + 440DE47B2482D59300C55354 /* PiholeAPI.swift in Sources */, + 44CD689624F5E96100115820 /* PiBarLabel.swift in Sources */, + 440DE47F2482D59F00C55354 /* UpdatePiholeOperation.swift in Sources */, + 440DE47D2482D59900C55354 /* PiBarManager.swift in Sources */, + 440DE4642482D37D00C55354 /* AppDelegate.swift in Sources */, + 448C6F202488286E004C30BE /* PiholeTableViewCell.swift in Sources */, + 440DE4802482D5A100C55354 /* ChangePiholeStatusOperation.swift in Sources */, + 440DE4662482D37D00C55354 /* SceneDelegate.swift in Sources */, + 440DE47C2482D59500C55354 /* Preferences.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 449395CE2471ABD600FA0C34 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -451,6 +724,22 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ + 440DE46B2482D37D00C55354 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 440DE46C2482D37D00C55354 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 440DE4702482D37D00C55354 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 440DE4712482D37D00C55354 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; 449395DB2471ABD700FA0C34 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -462,6 +751,49 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 440DE4752482D37D00C55354 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 61E78EDFF852EC8679380516 /* Pods-PiBar for iOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2Y9M69QJKZ; + INFOPLIST_FILE = "PiBar for iOS/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = net.amiantos.PiBar; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 440DE4762482D37D00C55354 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5D3D6C1A46DF2617F8C7B902 /* Pods-PiBar for iOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2Y9M69QJKZ; + INFOPLIST_FILE = "PiBar for iOS/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = net.amiantos.PiBar; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; 449395E02471ABD700FA0C34 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -488,6 +820,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -512,6 +845,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -548,6 +882,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -566,6 +901,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -577,10 +913,10 @@ }; 449395E32471ABD700FA0C34 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A8CE572444810D388817476D /* Pods-PiBar.debug.xcconfig */; + baseConfigurationReference = FADC58092194CBF2E4F9D976 /* Pods-PiBar for macOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = PiBar/PiBar.entitlements; + CODE_SIGN_ENTITLEMENTS = "PiBar for macOS/PiBar.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -591,7 +927,7 @@ "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/Mac", ); - INFOPLIST_FILE = PiBar/Info.plist; + INFOPLIST_FILE = "PiBar for macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -606,10 +942,10 @@ }; 449395E42471ABD700FA0C34 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 08A4966FFD2196F42B1CA80F /* Pods-PiBar.release.xcconfig */; + baseConfigurationReference = DCE170268940B6C31FF721B6 /* Pods-PiBar for macOS.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = PiBar/PiBar.entitlements; + CODE_SIGN_ENTITLEMENTS = "PiBar for macOS/PiBar.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -620,7 +956,7 @@ "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/Mac", ); - INFOPLIST_FILE = PiBar/Info.plist; + INFOPLIST_FILE = "PiBar for macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -636,6 +972,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 440DE4742482D37D00C55354 /* Build configuration list for PBXNativeTarget "PiBar for iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 440DE4752482D37D00C55354 /* Debug */, + 440DE4762482D37D00C55354 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 449395CD2471ABD600FA0C34 /* Build configuration list for PBXProject "PiBar" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -645,7 +990,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 449395E22471ABD700FA0C34 /* Build configuration list for PBXNativeTarget "PiBar" */ = { + 449395E22471ABD700FA0C34 /* Build configuration list for PBXNativeTarget "PiBar for macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 449395E32471ABD700FA0C34 /* Debug */, diff --git a/PiBar/Manager/Operations/UpdatePiholeOperation.swift b/PiBar/Manager/Operations/UpdatePiholeOperation.swift deleted file mode 100644 index 5874b55..0000000 --- a/PiBar/Manager/Operations/UpdatePiholeOperation.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// UpdatePiholeOperation.swift -// PiBar -// -// Created by Brad Root on 5/26/20. -// Copyright © 2020 Brad Root. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import Foundation - -final class UpdatePiholeOperation: AsyncOperation { - private(set) var pihole: Pihole - - init(_ pihole: Pihole) { - self.pihole = pihole - } - - override func main() { - Log.debug("Updating Pi-hole: \(pihole.identifier)") - pihole.api.fetchSummary { summary in - Log.debug("Received Summary for \(self.pihole.identifier)") - var enabled: Bool? = true - var online = true - var canBeManaged: Bool = false - - if let summary = summary { - if summary.status != "enabled" { - enabled = false - } - if !self.pihole.api.connection.token.isEmpty || !self.pihole.api.connection.passwordProtected { - canBeManaged = true - } - } else { - enabled = nil - online = false - canBeManaged = false - } - - let updatedPihole: Pihole = Pihole( - api: self.pihole.api, - identifier: self.pihole.api.identifier, - online: online, - summary: summary, - canBeManaged: canBeManaged, - enabled: enabled - ) - - self.pihole = updatedPihole - - self.state = .isFinished - } - } -} diff --git a/Podfile b/Podfile index 03ca86a..c94ce27 100644 --- a/Podfile +++ b/Podfile @@ -1,12 +1,17 @@ -# Uncomment the next line to define a global platform for your project -platform :osx, '10.12' - -target 'PiBar' do - # Comment the next line if you don't want to use dynamic frameworks +target 'PiBar for macOS' do + platform :osx, '10.12' use_frameworks! - # Pods for PiBar pod 'SwiftLint' pod 'HotKey' end + +target 'PiBar for iOS' do + platform :ios, '11.4' + use_frameworks! + + pod 'SwiftLint' + pod 'Charts' + +end diff --git a/Podfile.lock b/Podfile.lock index 5287612..2ffc46d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,20 +1,26 @@ PODS: + - Charts (3.6.0): + - Charts/Core (= 3.6.0) + - Charts/Core (3.6.0) - HotKey (0.1.2) - - SwiftLint (0.39.2) + - SwiftLint (0.40.3) DEPENDENCIES: + - Charts - HotKey - SwiftLint SPEC REPOS: trunk: + - Charts - HotKey - SwiftLint SPEC CHECKSUMS: + Charts: b1e3a1f5a1c9ba5394438ca3b91bd8c9076310af HotKey: ad59450195936c10992438c4210f673de5aee43e - SwiftLint: 22ccbbe3b8008684be5955693bab135e0ed6a447 + SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 -PODFILE CHECKSUM: bc53a454bd1292998c5646f550d93a401c3809d2 +PODFILE CHECKSUM: c2e80d933e60ddedf45d9176975578a449f44603 -COCOAPODS: 1.9.1 +COCOAPODS: 1.10.0 diff --git a/Pods/Charts/LICENSE b/Pods/Charts/LICENSE new file mode 100644 index 0000000..8198c7a --- /dev/null +++ b/Pods/Charts/LICENSE @@ -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-2019 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. + diff --git a/Pods/Charts/README.md b/Pods/Charts/README.md new file mode 100644 index 0000000..a469fbd --- /dev/null +++ b/Pods/Charts/README.md @@ -0,0 +1,227 @@ +**Version 3.5.0**, 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`~ +### One more heads up: As Swift evolves, if you are not using the latest Swift compiler, you shouldn't check out the master branch. Instead, you should go to the release page and pick up whatever suits you. + +* Xcode 11 / Swift 5 (master branch) +* 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, but there was no decent solution to create charts 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 5.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 +#### Video tutorials + +* [Chart in Swift - Setting Up a Basic Line Chart Using iOS Charts(Alex Nagy)](https://www.youtube.com/watch?v=mWhwe_tLNE8&list=PL_csAAO9PQ8bjzg-wxEff1Fr0Y5W1hrum&index=5) + +#### Blog posts +* [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) +* [Creating a Radar Chart in Swift (David Piper)](https://medium.com/@HeyDaveTheDev/creating-a-radar-chart-in-swift-5791afcf92f0) +* [Plotting in IOS using Charts framework with SwiftUI (Evgeny Basisty)](https://medium.com/@zzzzbh/plotting-in-ios-using-charts-framework-with-swiftui-222034a2bea6) + +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.5.0 +github "danielgindi/Charts" ~> 3.5.0 +``` + +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 Charts Demo project in both Objective-C and Swift ([**ChartsDemo-iOS**](https://github.com/danielgindi/Charts/tree/master/ChartsDemo-iOS), as well as macOS [**ChartsDemo-macOS**](https://github.com/danielgindi/Charts/tree/master/ChartsDemo-macOS)) 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. + +### Our amazing sponsors + +[Debricked](https://debricked.com/): Use open source securely + +[![debricked](https://user-images.githubusercontent.com/4375169/73585544-25bfa800-44dd-11ea-9661-82519a125302.jpg)](https://debricked.com/) + + +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. diff --git a/Pods/Charts/Source/Charts/Animation/Animator.swift b/Pods/Charts/Source/Charts/Animation/Animator.swift new file mode 100644 index 0000000..3e4aedd --- /dev/null +++ b/Pods/Charts/Source/Charts/Animation/Animator.swift @@ -0,0 +1,259 @@ +// +// 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 CoreGraphics +import Foundation +import QuartzCore + +@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? + + override public init() { + super.init() + } + + deinit { + stop() + } + + @objc open func stop() { + guard _displayLink != nil else { return } + + _displayLink?.remove(from: .main, forMode: RunLoop.Mode.common) + _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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingX: an easing function for the animation on the x axis + /// - 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: RunLoop.Mode.common) + } + } + + /// 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingOptionX: the easing function for the animation on the x axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - 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: RunLoop.Mode.common) + } + } + + /// 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - 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. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + /// - 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: RunLoop.Mode.common) + } + } + + /// 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. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + /// - easingOption: the easing function for the animation + @objc open func animate(yAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine) { + animate(yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption)) + } +} diff --git a/Pods/Charts/Source/Charts/Animation/ChartAnimationEasing.swift b/Pods/Charts/Source/Charts/Animation/ChartAnimationEasing.swift new file mode 100644 index 0000000..0ad70ad --- /dev/null +++ b/Pods/Charts/Source/Charts/Animation/ChartAnimationEasing.swift @@ -0,0 +1,364 @@ +// +// 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 CoreGraphics +import Foundation + +@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 enum EasingFunctions { + internal static let Linear = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in 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 + (elapsed == 0) ? 0.0 : Double(pow(2.0, 10.0 * (elapsed / duration - 1.0))) + } + + internal static let EaseOutExpo = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + (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 + 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 + } +} diff --git a/Pods/Charts/Source/Charts/Charts/BarChartView.swift b/Pods/Charts/Source/Charts/Charts/BarChartView.swift new file mode 100644 index 0000000..4be43a2 --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/BarChartView.swift @@ -0,0 +1,174 @@ +// +// 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 CoreGraphics +import Foundation + +/// 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 + + override internal func initialize() { + super.initialize() + + renderer = BarChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + + highlighter = BarHighlighter(chart: self) + + xAxis.spaceMin = 0.5 + xAxis.spaceMax = 0.5 + } + + override internal 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. + override open func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight? { + if _data === nil { + Swift.print("Can't select by touch. No data set.") + return nil + } + + guard let h = 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. + /// + /// - Parameters: + /// - fromX: the starting point on the x-axis where the grouping should begin + /// - groupSpace: the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f + /// - 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. + /// + /// - Parameters: + /// - x: + /// - dataSetIndex: + /// - 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 { _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 { _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 + + /// `true` the highlight is be full-bar oriented, `false` ifsingle-value + open var isHighlightFullBarEnabled: Bool { highlightFullBarEnabled } + + // MARK: - BarChartDataProvider + + open var barData: BarChartData? { _data as? BarChartData } + + /// `true` if drawing values above bars is enabled, `false` ifnot + open var isDrawValueAboveBarEnabled: Bool { drawValueAboveBarEnabled } + + /// `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot + open var isDrawBarShadowEnabled: Bool { drawBarShadowEnabled } +} diff --git a/Pods/Charts/Source/Charts/Charts/BarLineChartViewBase.swift b/Pods/Charts/Source/Charts/Charts/BarLineChartViewBase.swift new file mode 100644 index 0000000..6d29f38 --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/BarLineChartViewBase.swift @@ -0,0 +1,1738 @@ +// +// BarLineChartViewBase.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +/// Base-class of LineChart, BarChart, ScatterChart and CandleStickChart. +open class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChartDataProvider, NSUIGestureRecognizerDelegate { + /// the maximum number of entries to which values will be drawn + /// (entry numbers greater than this value will cause value-labels to disappear) + internal var _maxVisibleCount = 100 + + /// flag that indicates if auto scaling on the y axis is enabled + private var _autoScaleMinMaxEnabled = false + + private var _pinchZoomEnabled = false + private var _doubleTapToZoomEnabled = true + private var _dragXEnabled = true + private var _dragYEnabled = true + + private var _scaleXEnabled = true + private var _scaleYEnabled = true + + /// the color for the background of the chart-drawing area (everything behind the grid lines). + @objc open var gridBackgroundColor = NSUIColor(red: 240 / 255.0, green: 240 / 255.0, blue: 240 / 255.0, alpha: 1.0) + + @objc open var borderColor = NSUIColor.black + @objc open var borderLineWidth: CGFloat = 1.0 + + /// flag indicating if the grid background should be drawn or not + @objc open var drawGridBackgroundEnabled = false + + /// When enabled, the borders rectangle will be rendered. + /// If this is enabled, there is no point drawing the axis-lines of x- and y-axis. + @objc open var drawBordersEnabled = false + + /// When enabled, the values will be clipped to contentRect, otherwise they can bleed outside the content rect. + @objc open var clipValuesToContentEnabled: Bool = false + + /// When disabled, the data and/or highlights will not be clipped to contentRect. Disabling this option can + /// be useful, when the data lies fully within the content rect, but is drawn in such a way (such as thick lines) + /// that there is unwanted clipping. + @objc open var clipDataToContentEnabled: Bool = true + + /// Sets the minimum offset (padding) around the chart, defaults to 10 + @objc open var minOffset = CGFloat(10.0) + + /// Sets whether the chart should keep its position (zoom / scroll) after a rotation (orientation change) + /// **default**: false + @objc open var keepPositionOnRotation: Bool = false + + /// The left y-axis object. In the horizontal bar-chart, this is the + /// top axis. + @objc open internal(set) var leftAxis = YAxis(position: .left) + + /// The right y-axis object. In the horizontal bar-chart, this is the + /// bottom axis. + @objc open internal(set) var rightAxis = YAxis(position: .right) + + /// The left Y axis renderer. This is a read-write property so you can set your own custom renderer here. + /// **default**: An instance of YAxisRenderer + @objc open lazy var leftYAxisRenderer = YAxisRenderer(viewPortHandler: _viewPortHandler, yAxis: leftAxis, transformer: _leftAxisTransformer) + + /// The right Y axis renderer. This is a read-write property so you can set your own custom renderer here. + /// **default**: An instance of YAxisRenderer + @objc open lazy var rightYAxisRenderer = YAxisRenderer(viewPortHandler: _viewPortHandler, yAxis: rightAxis, transformer: _rightAxisTransformer) + + internal var _leftAxisTransformer: Transformer! + internal var _rightAxisTransformer: Transformer! + + /// The X axis renderer. This is a read-write property so you can set your own custom renderer here. + /// **default**: An instance of XAxisRenderer + @objc open lazy var xAxisRenderer = XAxisRenderer(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer) + + internal var _tapGestureRecognizer: NSUITapGestureRecognizer! + internal var _doubleTapGestureRecognizer: NSUITapGestureRecognizer! + #if !os(tvOS) + internal var _pinchGestureRecognizer: NSUIPinchGestureRecognizer! + #endif + internal var _panGestureRecognizer: NSUIPanGestureRecognizer! + + /// flag that indicates if a custom viewport offset has been set + private var _customViewPortEnabled = false + + override public init(frame: CGRect) { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + deinit { + stopDeceleration() + } + + override internal func initialize() { + super.initialize() + + _leftAxisTransformer = Transformer(viewPortHandler: _viewPortHandler) + _rightAxisTransformer = Transformer(viewPortHandler: _viewPortHandler) + + highlighter = ChartHighlighter(chart: self) + + _tapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))) + _doubleTapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(doubleTapGestureRecognized(_:))) + _doubleTapGestureRecognizer.nsuiNumberOfTapsRequired = 2 + _panGestureRecognizer = NSUIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:))) + + _panGestureRecognizer.delegate = self + + addGestureRecognizer(_tapGestureRecognizer) + addGestureRecognizer(_doubleTapGestureRecognizer) + addGestureRecognizer(_panGestureRecognizer) + + _doubleTapGestureRecognizer.isEnabled = _doubleTapToZoomEnabled + _panGestureRecognizer.isEnabled = _dragXEnabled || _dragYEnabled + + #if !os(tvOS) + _pinchGestureRecognizer = NSUIPinchGestureRecognizer(target: self, action: #selector(BarLineChartViewBase.pinchGestureRecognized(_:))) + _pinchGestureRecognizer.delegate = self + addGestureRecognizer(_pinchGestureRecognizer) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + + override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + // Saving current position of chart. + var oldPoint: CGPoint? + if keepPositionOnRotation, keyPath == "frame" || keyPath == "bounds" { + oldPoint = viewPortHandler.contentRect.origin + getTransformer(forAxis: .left).pixelToValues(&oldPoint!) + } + + // Superclass transforms chart. + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + + // Restoring old position of chart + if var newPoint = oldPoint, keepPositionOnRotation { + getTransformer(forAxis: .left).pointValueToPixel(&newPoint) + viewPortHandler.centerViewPort(pt: newPoint, chart: self) + } else { + viewPortHandler.refresh(newMatrix: viewPortHandler.touchMatrix, chart: self, invalidate: true) + } + } + + override open func draw(_ rect: CGRect) { + super.draw(rect) + + guard data != nil, let renderer = renderer else { return } + + let optionalContext = NSUIGraphicsGetCurrentContext() + guard let context = optionalContext else { return } + + // execute all drawing commands + drawGridBackground(context: context) + + if _autoScaleMinMaxEnabled { + autoScale() + } + + if leftAxis.isEnabled { + leftYAxisRenderer.computeAxis(min: leftAxis._axisMinimum, max: leftAxis._axisMaximum, inverted: leftAxis.isInverted) + } + + if rightAxis.isEnabled { + rightYAxisRenderer.computeAxis(min: rightAxis._axisMinimum, max: rightAxis._axisMaximum, inverted: rightAxis.isInverted) + } + + if _xAxis.isEnabled { + xAxisRenderer.computeAxis(min: _xAxis._axisMinimum, max: _xAxis._axisMaximum, inverted: false) + } + + xAxisRenderer.renderAxisLine(context: context) + leftYAxisRenderer.renderAxisLine(context: context) + rightYAxisRenderer.renderAxisLine(context: context) + + // The renderers are responsible for clipping, to account for line-width center etc. + if xAxis.drawGridLinesBehindDataEnabled { + xAxisRenderer.renderGridLines(context: context) + leftYAxisRenderer.renderGridLines(context: context) + rightYAxisRenderer.renderGridLines(context: context) + } + + if _xAxis.isEnabled, _xAxis.isDrawLimitLinesBehindDataEnabled { + xAxisRenderer.renderLimitLines(context: context) + } + + if leftAxis.isEnabled, leftAxis.isDrawLimitLinesBehindDataEnabled { + leftYAxisRenderer.renderLimitLines(context: context) + } + + if rightAxis.isEnabled, rightAxis.isDrawLimitLinesBehindDataEnabled { + rightYAxisRenderer.renderLimitLines(context: context) + } + + context.saveGState() + // make sure the data cannot be drawn outside the content-rect + if clipDataToContentEnabled { + context.clip(to: _viewPortHandler.contentRect) + } + renderer.drawData(context: context) + + // The renderers are responsible for clipping, to account for line-width center etc. + if !xAxis.drawGridLinesBehindDataEnabled { + xAxisRenderer.renderGridLines(context: context) + leftYAxisRenderer.renderGridLines(context: context) + rightYAxisRenderer.renderGridLines(context: context) + } + + // if highlighting is enabled + if valuesToHighlight() { + renderer.drawHighlighted(context: context, indices: _indicesToHighlight) + } + + context.restoreGState() + + renderer.drawExtras(context: context) + + if _xAxis.isEnabled, !_xAxis.isDrawLimitLinesBehindDataEnabled { + xAxisRenderer.renderLimitLines(context: context) + } + + if leftAxis.isEnabled, !leftAxis.isDrawLimitLinesBehindDataEnabled { + leftYAxisRenderer.renderLimitLines(context: context) + } + + if rightAxis.isEnabled, !rightAxis.isDrawLimitLinesBehindDataEnabled { + rightYAxisRenderer.renderLimitLines(context: context) + } + + xAxisRenderer.renderAxisLabels(context: context) + leftYAxisRenderer.renderAxisLabels(context: context) + rightYAxisRenderer.renderAxisLabels(context: context) + + if clipValuesToContentEnabled { + context.saveGState() + context.clip(to: _viewPortHandler.contentRect) + + renderer.drawValues(context: context) + + context.restoreGState() + } else { + renderer.drawValues(context: context) + } + + _legendRenderer.renderLegend(context: context) + + drawDescription(context: context) + + drawMarkers(context: context) + } + + private var _autoScaleLastLowestVisibleX: Double? + private var _autoScaleLastHighestVisibleX: Double? + + /// Performs auto scaling of the axis by recalculating the minimum and maximum y-values based on the entries currently in view. + internal func autoScale() { + guard let data = _data + else { return } + + data.calcMinMaxY(fromX: lowestVisibleX, toX: highestVisibleX) + + _xAxis.calculate(min: data.xMin, max: data.xMax) + + // calculate axis range (min / max) according to provided data + + if leftAxis.isEnabled { + leftAxis.calculate(min: data.getYMin(axis: .left), max: data.getYMax(axis: .left)) + } + + if rightAxis.isEnabled { + rightAxis.calculate(min: data.getYMin(axis: .right), max: data.getYMax(axis: .right)) + } + + calculateOffsets() + } + + internal func prepareValuePxMatrix() { + _rightAxisTransformer.prepareMatrixValuePx(chartXMin: _xAxis._axisMinimum, deltaX: CGFloat(xAxis.axisRange), deltaY: CGFloat(rightAxis.axisRange), chartYMin: rightAxis._axisMinimum) + _leftAxisTransformer.prepareMatrixValuePx(chartXMin: xAxis._axisMinimum, deltaX: CGFloat(xAxis.axisRange), deltaY: CGFloat(leftAxis.axisRange), chartYMin: leftAxis._axisMinimum) + } + + internal func prepareOffsetMatrix() { + _rightAxisTransformer.prepareMatrixOffset(inverted: rightAxis.isInverted) + _leftAxisTransformer.prepareMatrixOffset(inverted: leftAxis.isInverted) + } + + override open func notifyDataSetChanged() { + renderer?.initBuffers() + + calcMinMax() + + leftYAxisRenderer.computeAxis(min: leftAxis._axisMinimum, max: leftAxis._axisMaximum, inverted: leftAxis.isInverted) + rightYAxisRenderer.computeAxis(min: rightAxis._axisMinimum, max: rightAxis._axisMaximum, inverted: rightAxis.isInverted) + + if let data = _data { + xAxisRenderer.computeAxis( + min: _xAxis._axisMinimum, + max: _xAxis._axisMaximum, + inverted: false + ) + + if _legend !== nil { + legendRenderer?.computeLegend(data: data) + } + } + + calculateOffsets() + + setNeedsDisplay() + } + + override internal func calcMinMax() { + // calculate / set x-axis range + _xAxis.calculate(min: _data?.xMin ?? 0.0, max: _data?.xMax ?? 0.0) + + // calculate axis range (min / max) according to provided data + leftAxis.calculate(min: _data?.getYMin(axis: .left) ?? 0.0, max: _data?.getYMax(axis: .left) ?? 0.0) + rightAxis.calculate(min: _data?.getYMin(axis: .right) ?? 0.0, max: _data?.getYMax(axis: .right) ?? 0.0) + } + + internal func calculateLegendOffsets(offsetLeft: inout CGFloat, offsetTop: inout CGFloat, offsetRight: inout CGFloat, offsetBottom: inout CGFloat) { + // setup offsets for legend + if _legend !== nil, _legend.isEnabled, !_legend.drawInside { + 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 + + case .bottom: + offsetBottom += min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent) + _legend.yOffset + + default: + break + } + } + } + } + + override internal func calculateOffsets() { + if !_customViewPortEnabled { + var offsetLeft = CGFloat(0.0) + var offsetRight = CGFloat(0.0) + var offsetTop = CGFloat(0.0) + var offsetBottom = CGFloat(0.0) + + calculateLegendOffsets(offsetLeft: &offsetLeft, + offsetTop: &offsetTop, + offsetRight: &offsetRight, + offsetBottom: &offsetBottom) + + // offsets for y-labels + if leftAxis.needsOffset { + offsetLeft += leftAxis.requiredSize().width + } + + if rightAxis.needsOffset { + offsetRight += rightAxis.requiredSize().width + } + + if xAxis.isEnabled, xAxis.isDrawLabelsEnabled { + let xlabelheight = xAxis.labelRotatedHeight + xAxis.yOffset + + // offsets for x-labels + if xAxis.labelPosition == .bottom { + offsetBottom += xlabelheight + } else if xAxis.labelPosition == .top { + offsetTop += xlabelheight + } else if xAxis.labelPosition == .bothSided { + offsetBottom += xlabelheight + offsetTop += xlabelheight + } + } + + offsetTop += extraTopOffset + offsetRight += extraRightOffset + offsetBottom += extraBottomOffset + offsetLeft += extraLeftOffset + + _viewPortHandler.restrainViewPort( + offsetLeft: max(minOffset, offsetLeft), + offsetTop: max(minOffset, offsetTop), + offsetRight: max(minOffset, offsetRight), + offsetBottom: max(minOffset, offsetBottom) + ) + } + + prepareOffsetMatrix() + prepareValuePxMatrix() + } + + /// draws the grid background + internal func drawGridBackground(context: CGContext) { + if drawGridBackgroundEnabled || drawBordersEnabled { + context.saveGState() + } + + if drawGridBackgroundEnabled { + // draw the grid background + context.setFillColor(gridBackgroundColor.cgColor) + context.fill(_viewPortHandler.contentRect) + } + + if drawBordersEnabled { + context.setLineWidth(borderLineWidth) + context.setStrokeColor(borderColor.cgColor) + context.stroke(_viewPortHandler.contentRect) + } + + if drawGridBackgroundEnabled || drawBordersEnabled { + context.restoreGState() + } + } + + // MARK: - Gestures + + private enum GestureScaleAxis { + case both + case x + case y + } + + private var _isDragging = false + private var _isScaling = false + private var _gestureScaleAxis = GestureScaleAxis.both + private var _closestDataSetToTouch: IChartDataSet! + private var _panGestureReachedEdge: Bool = false + private weak var _outerScrollView: NSUIScrollView? + + private var _lastPanPoint = CGPoint() /// This is to prevent using setTranslation which resets velocity + + private var _decelerationLastTime: TimeInterval = 0.0 + private var _decelerationDisplayLink: NSUIDisplayLink! + private var _decelerationVelocity = CGPoint() + + @objc private func tapGestureRecognized(_ recognizer: NSUITapGestureRecognizer) { + if _data === nil { + return + } + + if recognizer.state == NSUIGestureRecognizerState.ended { + if !isHighLightPerTapEnabled { return } + + let h = getHighlightByTouchPoint(recognizer.location(in: self)) + + if h === nil || h == lastHighlighted { + lastHighlighted = nil + highlightValue(nil, callDelegate: true) + } else { + lastHighlighted = h + highlightValue(h, callDelegate: true) + } + } + } + + @objc private func doubleTapGestureRecognized(_ recognizer: NSUITapGestureRecognizer) { + if _data === nil { + return + } + + if recognizer.state == NSUIGestureRecognizerState.ended { + if _data !== nil, _doubleTapToZoomEnabled, (data?.entryCount ?? 0) > 0 { + var location = recognizer.location(in: self) + location.x = location.x - _viewPortHandler.offsetLeft + + if isTouchInverted() { + location.y = -(location.y - _viewPortHandler.offsetTop) + } else { + location.y = -(bounds.size.height - location.y - _viewPortHandler.offsetBottom) + } + + let scaleX: CGFloat = isScaleXEnabled ? 1.4 : 1.0 + let scaleY: CGFloat = isScaleYEnabled ? 1.4 : 1.0 + + zoom(scaleX: scaleX, scaleY: scaleY, x: location.x, y: location.y) + delegate?.chartScaled?(self, scaleX: scaleX, scaleY: scaleY) + } + } + } + + #if !os(tvOS) + @objc private func pinchGestureRecognized(_ recognizer: NSUIPinchGestureRecognizer) { + if recognizer.state == NSUIGestureRecognizerState.began { + stopDeceleration() + + if _data !== nil && + (_pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled) + { + _isScaling = true + + if _pinchZoomEnabled { + _gestureScaleAxis = .both + } else { + let x = abs(recognizer.location(in: self).x - recognizer.nsuiLocationOfTouch(1, inView: self).x) + let y = abs(recognizer.location(in: self).y - recognizer.nsuiLocationOfTouch(1, inView: self).y) + + if _scaleXEnabled != _scaleYEnabled { + _gestureScaleAxis = _scaleXEnabled ? .x : .y + } else { + _gestureScaleAxis = x > y ? .x : .y + } + } + } + } else if recognizer.state == NSUIGestureRecognizerState.ended || + recognizer.state == NSUIGestureRecognizerState.cancelled + { + if _isScaling { + _isScaling = false + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + } else if recognizer.state == NSUIGestureRecognizerState.changed { + let isZoomingOut = (recognizer.nsuiScale < 1) + var canZoomMoreX = isZoomingOut ? _viewPortHandler.canZoomOutMoreX : _viewPortHandler.canZoomInMoreX + var canZoomMoreY = isZoomingOut ? _viewPortHandler.canZoomOutMoreY : _viewPortHandler.canZoomInMoreY + + if _isScaling { + canZoomMoreX = canZoomMoreX && _scaleXEnabled && (_gestureScaleAxis == .both || _gestureScaleAxis == .x) + canZoomMoreY = canZoomMoreY && _scaleYEnabled && (_gestureScaleAxis == .both || _gestureScaleAxis == .y) + if canZoomMoreX || canZoomMoreY { + var location = recognizer.location(in: self) + location.x = location.x - _viewPortHandler.offsetLeft + + if isTouchInverted() { + location.y = -(location.y - _viewPortHandler.offsetTop) + } else { + location.y = -(_viewPortHandler.chartHeight - location.y - _viewPortHandler.offsetBottom) + } + + let scaleX = canZoomMoreX ? recognizer.nsuiScale : 1.0 + let scaleY = canZoomMoreY ? recognizer.nsuiScale : 1.0 + + var matrix = CGAffineTransform(translationX: location.x, y: location.y) + matrix = matrix.scaledBy(x: scaleX, y: scaleY) + matrix = matrix.translatedBy(x: -location.x, y: -location.y) + + matrix = _viewPortHandler.touchMatrix.concatenating(matrix) + + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true) + + if delegate !== nil { + delegate?.chartScaled?(self, scaleX: scaleX, scaleY: scaleY) + } + } + + recognizer.nsuiScale = 1.0 + } + } + } + #endif + + @objc private func panGestureRecognized(_ recognizer: NSUIPanGestureRecognizer) { + if recognizer.state == NSUIGestureRecognizerState.began && recognizer.nsuiNumberOfTouches() > 0 { + stopDeceleration() + + if _data === nil || !isDragEnabled + { // If we have no data, we have nothing to pan and no data to highlight + return + } + + // If drag is enabled and we are in a position where there's something to drag: + // * If we're zoomed in, then obviously we have something to drag. + // * If we have a drag offset - we always have something to drag + if !hasNoDragOffset || !isFullyZoomedOut { + _isDragging = true + + _closestDataSetToTouch = getDataSetByTouchPoint(point: recognizer.nsuiLocationOfTouch(0, inView: self)) + + var translation = recognizer.translation(in: self) + if !dragXEnabled { + translation.x = 0.0 + } else if !dragYEnabled { + translation.y = 0.0 + } + + let didUserDrag = translation.x != 0.0 || translation.y != 0.0 + + // Check to see if user dragged at all and if so, can the chart be dragged by the given amount + if didUserDrag, !performPanChange(translation: translation) { + if _outerScrollView !== nil { + // We can stop dragging right now, and let the scroll view take control + _outerScrollView = nil + _isDragging = false + } + } else { + if _outerScrollView !== nil { + // Prevent the parent scroll view from scrolling + _outerScrollView?.nsuiIsScrollEnabled = false + } + } + + _lastPanPoint = recognizer.translation(in: self) + } else if isHighlightPerDragEnabled { + // We will only handle highlights on NSUIGestureRecognizerState.Changed + + _isDragging = false + } + } else if recognizer.state == NSUIGestureRecognizerState.changed { + if _isDragging { + let originalTranslation = recognizer.translation(in: self) + var translation = CGPoint(x: originalTranslation.x - _lastPanPoint.x, y: originalTranslation.y - _lastPanPoint.y) + + if !dragXEnabled { + translation.x = 0.0 + } else if !dragYEnabled { + translation.y = 0.0 + } + + _ = performPanChange(translation: translation) + + _lastPanPoint = originalTranslation + } else if isHighlightPerDragEnabled { + let h = getHighlightByTouchPoint(recognizer.location(in: self)) + + let lastHighlighted = self.lastHighlighted + + if h != lastHighlighted { + self.lastHighlighted = h + highlightValue(h, callDelegate: true) + } + } + } else if recognizer.state == NSUIGestureRecognizerState.ended || recognizer.state == NSUIGestureRecognizerState.cancelled { + if _isDragging { + if recognizer.state == NSUIGestureRecognizerState.ended, isDragDecelerationEnabled { + stopDeceleration() + + _decelerationLastTime = CACurrentMediaTime() + _decelerationVelocity = recognizer.velocity(in: self) + + _decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(BarLineChartViewBase.decelerationLoop)) + _decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) + } + + _isDragging = false + + delegate?.chartViewDidEndPanning?(self) + } + + if _outerScrollView !== nil { + _outerScrollView?.nsuiIsScrollEnabled = true + _outerScrollView = nil + } + } + } + + private func performPanChange(translation: CGPoint) -> Bool { + var translation = translation + + if isTouchInverted() { + if self is HorizontalBarChartView { + translation.x = -translation.x + } else { + translation.y = -translation.y + } + } + + let originalMatrix = _viewPortHandler.touchMatrix + + var matrix = CGAffineTransform(translationX: translation.x, y: translation.y) + matrix = originalMatrix.concatenating(matrix) + + matrix = _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true) + + if matrix != originalMatrix { + delegate?.chartTranslated?(self, dX: translation.x, dY: translation.y) + } + + // Did we managed to actually drag or did we reach the edge? + return matrix.tx != originalMatrix.tx || matrix.ty != originalMatrix.ty + } + + private func isTouchInverted() -> Bool { + isAnyAxisInverted && + _closestDataSetToTouch !== nil && + getAxis(_closestDataSetToTouch.axisDependency).isInverted + } + + @objc open func stopDeceleration() { + if _decelerationDisplayLink !== nil { + _decelerationDisplayLink.remove(from: RunLoop.main, forMode: RunLoop.Mode.common) + _decelerationDisplayLink = nil + } + } + + @objc private func decelerationLoop() { + let currentTime = CACurrentMediaTime() + + _decelerationVelocity.x *= dragDecelerationFrictionCoef + _decelerationVelocity.y *= dragDecelerationFrictionCoef + + let timeInterval = CGFloat(currentTime - _decelerationLastTime) + + let distance = CGPoint( + x: _decelerationVelocity.x * timeInterval, + y: _decelerationVelocity.y * timeInterval + ) + + if !performPanChange(translation: distance) { + // We reached the edge, stop + _decelerationVelocity.x = 0.0 + _decelerationVelocity.y = 0.0 + } + + _decelerationLastTime = currentTime + + if abs(_decelerationVelocity.x) < 0.001, abs(_decelerationVelocity.y) < 0.001 { + stopDeceleration() + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + } + + private func nsuiGestureRecognizerShouldBegin(_ gestureRecognizer: NSUIGestureRecognizer) -> Bool { + if gestureRecognizer == _panGestureRecognizer { + let velocity = _panGestureRecognizer.velocity(in: self) + if _data === nil || !isDragEnabled || + (hasNoDragOffset && isFullyZoomedOut && !isHighlightPerDragEnabled) || + (!_dragYEnabled && abs(velocity.y) > abs(velocity.x)) || + (!_dragXEnabled && abs(velocity.y) < abs(velocity.x)) + { + return false + } + } else { + #if !os(tvOS) + if gestureRecognizer == _pinchGestureRecognizer { + if _data === nil || (!_pinchZoomEnabled && !_scaleXEnabled && !_scaleYEnabled) { + return false + } + } + #endif + } + + return true + } + + #if !os(OSX) + override open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if !super.gestureRecognizerShouldBegin(gestureRecognizer) { + return false + } + + return nsuiGestureRecognizerShouldBegin(gestureRecognizer) + } + #endif + + #if os(OSX) + public func gestureRecognizerShouldBegin(gestureRecognizer: NSGestureRecognizer) -> Bool { + nsuiGestureRecognizerShouldBegin(gestureRecognizer) + } + #endif + + open func gestureRecognizer(_ gestureRecognizer: NSUIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: NSUIGestureRecognizer) -> Bool { + #if !os(tvOS) + if (gestureRecognizer is NSUIPinchGestureRecognizer && otherGestureRecognizer is NSUIPanGestureRecognizer) || + (gestureRecognizer is NSUIPanGestureRecognizer && otherGestureRecognizer is NSUIPinchGestureRecognizer) + { + return true + } + #endif + + if gestureRecognizer is NSUIPanGestureRecognizer, + otherGestureRecognizer is NSUIPanGestureRecognizer, + gestureRecognizer == _panGestureRecognizer + { + var scrollView = superview + while scrollView != nil, !(scrollView is NSUIScrollView) { + scrollView = scrollView?.superview + } + + // If there is two scrollview together, we pick the superview of the inner scrollview. + // In the case of UITableViewWrepperView, the superview will be UITableView + if let superViewOfScrollView = scrollView?.superview, + superViewOfScrollView is NSUIScrollView + { + scrollView = superViewOfScrollView + } + + var foundScrollView = scrollView as? NSUIScrollView + + if !(foundScrollView?.nsuiIsScrollEnabled ?? true) { + foundScrollView = nil + } + + let scrollViewPanGestureRecognizer = foundScrollView?.nsuiGestureRecognizers?.first { + $0 is NSUIPanGestureRecognizer + } + + if otherGestureRecognizer === scrollViewPanGestureRecognizer { + _outerScrollView = foundScrollView + + return true + } + } + + return false + } + + // MARK: Viewport modifiers + + /// Zooms in by 1.4, into the charts center. + @objc open func zoomIn() { + let center = _viewPortHandler.contentCenter + + let matrix = _viewPortHandler.zoomIn(x: center.x, y: -center.y) + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + + /// Zooms out by 0.7, from the charts center. + @objc open func zoomOut() { + let center = _viewPortHandler.contentCenter + + let matrix = _viewPortHandler.zoomOut(x: center.x, y: -center.y) + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + + /// Zooms out to original size. + @objc open func resetZoom() { + let matrix = _viewPortHandler.resetZoom() + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + + /// Zooms in or out by the given scale factor. x and y are the coordinates + /// (in pixels) of the zoom center. + /// + /// - Parameters: + /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in + /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in + /// - x: + /// - y: + @objc open func zoom( + scaleX: CGFloat, + scaleY: CGFloat, + x: CGFloat, + y: CGFloat + ) { + let matrix = _viewPortHandler.zoom(scaleX: scaleX, scaleY: scaleY, x: x, y: -y) + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + + /// Zooms in or out by the given scale factor. + /// x and y are the values (**not pixels**) of the zoom center. + /// + /// - Parameters: + /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in + /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in + /// - xValue: + /// - yValue: + /// - axis: + @objc open func zoom( + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency + ) { + let job = ZoomViewJob( + viewPortHandler: viewPortHandler, + scaleX: scaleX, + scaleY: scaleY, + xValue: xValue, + yValue: yValue, + transformer: getTransformer(forAxis: axis), + axis: axis, + view: self + ) + addViewportJob(job) + } + + /// Zooms to the center of the chart with the given scale factor. + /// + /// - Parameters: + /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in + /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in + /// - xValue: + /// - yValue: + /// - axis: + @objc open func zoomToCenter( + scaleX: CGFloat, + scaleY: CGFloat + ) { + let center = centerOffsets + let matrix = viewPortHandler.zoom( + scaleX: scaleX, + scaleY: scaleY, + x: center.x, + y: -center.y + ) + viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + } + + /// Zooms by the specified scale factor to the specified values on the specified axis. + /// + /// - Parameters: + /// - scaleX: + /// - scaleY: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func zoomAndCenterViewAnimated( + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easing: ChartEasingFunctionBlock? + ) { + let origin = valueForTouchPoint( + point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), + axis: axis + ) + + let job = AnimatedZoomViewJob( + viewPortHandler: viewPortHandler, + transformer: getTransformer(forAxis: axis), + view: self, + yAxis: getAxis(axis), + xAxisRange: _xAxis.axisRange, + scaleX: scaleX, + scaleY: scaleY, + xOrigin: viewPortHandler.scaleX, + yOrigin: viewPortHandler.scaleY, + zoomCenterX: CGFloat(xValue), + zoomCenterY: CGFloat(yValue), + zoomOriginX: origin.x, + zoomOriginY: origin.y, + duration: duration, + easing: easing + ) + + addViewportJob(job) + } + + /// Zooms by the specified scale factor to the specified values on the specified axis. + /// + /// - Parameters: + /// - scaleX: + /// - scaleY: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func zoomAndCenterViewAnimated( + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easingOption: ChartEasingOption + ) { + zoomAndCenterViewAnimated(scaleX: scaleX, scaleY: scaleY, xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) + } + + /// Zooms by the specified scale factor to the specified values on the specified axis. + /// + /// - Parameters: + /// - scaleX: + /// - scaleY: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func zoomAndCenterViewAnimated( + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval + ) { + zoomAndCenterViewAnimated(scaleX: scaleX, scaleY: scaleY, xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) + } + + /// Resets all zooming and dragging and makes the chart fit exactly it's bounds. + @objc open func fitScreen() { + let matrix = _viewPortHandler.fitScreen() + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + calculateOffsets() + setNeedsDisplay() + } + + /// Sets the minimum scale value to which can be zoomed out. 1 = fitScreen + @objc open func setScaleMinima(_ scaleX: CGFloat, scaleY: CGFloat) { + _viewPortHandler.setMinimumScaleX(scaleX) + _viewPortHandler.setMinimumScaleY(scaleY) + } + + @objc open var visibleXRange: Double { + abs(highestVisibleX - lowestVisibleX) + } + + /// Sets the size of the area (range on the x-axis) that should be maximum visible at once (no further zooming out allowed). + /// + /// If this is e.g. set to 10, no more than a range of 10 values on the x-axis can be viewed at once without scrolling. + /// + /// If you call this method, chart must have data or it has no effect. + @objc open func setVisibleXRangeMaximum(_ maxXRange: Double) { + let xScale = _xAxis.axisRange / maxXRange + _viewPortHandler.setMinimumScaleX(CGFloat(xScale)) + } + + /// Sets the size of the area (range on the x-axis) that should be minimum visible at once (no further zooming in allowed). + /// + /// If this is e.g. set to 10, no less than a range of 10 values on the x-axis can be viewed at once without scrolling. + /// + /// If you call this method, chart must have data or it has no effect. + @objc open func setVisibleXRangeMinimum(_ minXRange: Double) { + let xScale = _xAxis.axisRange / minXRange + _viewPortHandler.setMaximumScaleX(CGFloat(xScale)) + } + + /// Limits the maximum and minimum value count that can be visible by pinching and zooming. + /// + /// e.g. minRange=10, maxRange=100 no less than 10 values and no more that 100 values can be viewed + /// at once without scrolling. + /// + /// If you call this method, chart must have data or it has no effect. + @objc open func setVisibleXRange(minXRange: Double, maxXRange: Double) { + let minScale = _xAxis.axisRange / maxXRange + let maxScale = _xAxis.axisRange / minXRange + _viewPortHandler.setMinMaxScaleX( + minScaleX: CGFloat(minScale), + maxScaleX: CGFloat(maxScale) + ) + } + + /// Sets the size of the area (range on the y-axis) that should be maximum visible at once. + /// + /// - Parameters: + /// - yRange: + /// - axis: - the axis for which this limit should apply + @objc open func setVisibleYRangeMaximum(_ maxYRange: Double, axis: YAxis.AxisDependency) { + let yScale = getAxisRange(axis: axis) / maxYRange + _viewPortHandler.setMinimumScaleY(CGFloat(yScale)) + } + + /// Sets the size of the area (range on the y-axis) that should be minimum visible at once, no further zooming in possible. + /// + /// - Parameters: + /// - yRange: + /// - axis: - the axis for which this limit should apply + @objc open func setVisibleYRangeMinimum(_ minYRange: Double, axis: YAxis.AxisDependency) { + let yScale = getAxisRange(axis: axis) / minYRange + _viewPortHandler.setMaximumScaleY(CGFloat(yScale)) + } + + /// Limits the maximum and minimum y range that can be visible by pinching and zooming. + /// + /// - Parameters: + /// - minYRange: + /// - maxYRange: + /// - axis: + @objc open func setVisibleYRange(minYRange: Double, maxYRange: Double, axis: YAxis.AxisDependency) { + let minScale = getAxisRange(axis: axis) / minYRange + let maxScale = getAxisRange(axis: axis) / maxYRange + _viewPortHandler.setMinMaxScaleY(minScaleY: CGFloat(minScale), maxScaleY: CGFloat(maxScale)) + } + + /// Moves the left side of the current viewport to the specified x-value. + /// This also refreshes the chart by calling setNeedsDisplay(). + @objc open func moveViewToX(_ xValue: Double) { + let job = MoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: 0.0, + transformer: getTransformer(forAxis: .left), + view: self + ) + + addViewportJob(job) + } + + /// Centers the viewport to the specified y-value on the y-axis. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - yValue: + /// - axis: - which axis should be used as a reference for the y-axis + @objc open func moveViewToY(_ yValue: Double, axis: YAxis.AxisDependency) { + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + + let job = MoveViewJob( + viewPortHandler: viewPortHandler, + xValue: 0.0, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self + ) + + addViewportJob(job) + } + + /// This will move the left side of the current viewport to the specified x-value on the x-axis, and center the viewport to the specified y-value on the y-axis. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: - which axis should be used as a reference for the y-axis + @objc open func moveViewTo(xValue: Double, yValue: Double, axis: YAxis.AxisDependency) { + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + + let job = MoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self + ) + + addViewportJob(job) + } + + /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func moveViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easing: ChartEasingFunctionBlock? + ) { + let bounds = valueForTouchPoint( + point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), + axis: axis + ) + + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + + let job = AnimatedMoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self, + xOrigin: bounds.x, + yOrigin: bounds.y, + duration: duration, + easing: easing + ) + + addViewportJob(job) + } + + /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func moveViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easingOption: ChartEasingOption + ) { + moveViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) + } + + /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func moveViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval + ) { + moveViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) + } + + /// This will move the center of the current viewport to the specified x-value and y-value. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: - which axis should be used as a reference for the y-axis + @objc open func centerViewTo( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency + ) { + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + let xInView = xAxis.axisRange / Double(_viewPortHandler.scaleX) + + let job = MoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue - xInView / 2.0, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self + ) + + addViewportJob(job) + } + + /// This will move the center of the current viewport to the specified x-value and y-value animated. + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func centerViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easing: ChartEasingFunctionBlock? + ) { + let bounds = valueForTouchPoint( + point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), + axis: axis + ) + + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + let xInView = xAxis.axisRange / Double(_viewPortHandler.scaleX) + + let job = AnimatedMoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue - xInView / 2.0, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self, + xOrigin: bounds.x, + yOrigin: bounds.y, + duration: duration, + easing: easing + ) + + addViewportJob(job) + } + + /// This will move the center of the current viewport to the specified x-value and y-value animated. + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func centerViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easingOption: ChartEasingOption + ) { + centerViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) + } + + /// This will move the center of the current viewport to the specified x-value and y-value animated. + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func centerViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval + ) { + centerViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) + } + + /// Sets custom offsets for the current `ChartViewPort` (the offsets on the sides of the actual chart window). Setting this will prevent the chart from automatically calculating it's offsets. Use `resetViewPortOffsets()` to undo this. + /// ONLY USE THIS WHEN YOU KNOW WHAT YOU ARE DOING, else use `setExtraOffsets(...)`. + @objc open func setViewPortOffsets(left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) { + _customViewPortEnabled = true + + if Thread.isMainThread { + _viewPortHandler.restrainViewPort(offsetLeft: left, offsetTop: top, offsetRight: right, offsetBottom: bottom) + prepareOffsetMatrix() + prepareValuePxMatrix() + } else { + DispatchQueue.main.async { + self.setViewPortOffsets(left: left, top: top, right: right, bottom: bottom) + } + } + } + + /// Resets all custom offsets set via `setViewPortOffsets(...)` method. Allows the chart to again calculate all offsets automatically. + @objc open func resetViewPortOffsets() { + _customViewPortEnabled = false + calculateOffsets() + } + + // MARK: - Accessors + + /// - Returns: The range of the specified axis. + @objc open func getAxisRange(axis: YAxis.AxisDependency) -> Double { + if axis == .left { + return leftAxis.axisRange + } else { + return rightAxis.axisRange + } + } + + /// - Returns: The position (in pixels) the provided Entry has inside the chart view + @objc open func getPosition(entry e: ChartDataEntry, axis: YAxis.AxisDependency) -> CGPoint { + var vals = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y)) + + getTransformer(forAxis: axis).pointValueToPixel(&vals) + + return vals + } + + /// is dragging enabled? (moving the chart with the finger) for the chart (this does not affect scaling). + @objc open var dragEnabled: Bool { + get { + _dragXEnabled || _dragYEnabled + } + set { + _dragYEnabled = newValue + _dragXEnabled = newValue + } + } + + /// is dragging enabled? (moving the chart with the finger) for the chart (this does not affect scaling). + @objc open var isDragEnabled: Bool { + dragEnabled + } + + /// is dragging on the X axis enabled? + @objc open var dragXEnabled: Bool { + get { + _dragXEnabled + } + set { + _dragXEnabled = newValue + } + } + + /// is dragging on the Y axis enabled? + @objc open var dragYEnabled: Bool { + get { + _dragYEnabled + } + set { + _dragYEnabled = newValue + } + } + + /// is scaling enabled? (zooming in and out by gesture) for the chart (this does not affect dragging). + @objc open func setScaleEnabled(_ enabled: Bool) { + if _scaleXEnabled != enabled || _scaleYEnabled != enabled { + _scaleXEnabled = enabled + _scaleYEnabled = enabled + #if !os(tvOS) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + } + + @objc open var scaleXEnabled: Bool { + get { + _scaleXEnabled + } + set { + if _scaleXEnabled != newValue { + _scaleXEnabled = newValue + #if !os(tvOS) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + } + } + + @objc open var scaleYEnabled: Bool { + get { + _scaleYEnabled + } + set { + if _scaleYEnabled != newValue { + _scaleYEnabled = newValue + #if !os(tvOS) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + } + } + + @objc open var isScaleXEnabled: Bool { scaleXEnabled } + @objc open var isScaleYEnabled: Bool { scaleYEnabled } + + /// flag that indicates if double tap zoom is enabled or not + @objc open var doubleTapToZoomEnabled: Bool { + get { + _doubleTapToZoomEnabled + } + set { + if _doubleTapToZoomEnabled != newValue { + _doubleTapToZoomEnabled = newValue + _doubleTapGestureRecognizer.isEnabled = _doubleTapToZoomEnabled + } + } + } + + /// **default**: true + /// `true` if zooming via double-tap is enabled `false` ifnot. + @objc open var isDoubleTapToZoomEnabled: Bool { + doubleTapToZoomEnabled + } + + /// flag that indicates if highlighting per dragging over a fully zoomed out chart is enabled + @objc open var highlightPerDragEnabled = true + + /// If set to true, highlighting per dragging over a fully zoomed out chart is enabled + /// You might want to disable this when using inside a `NSUIScrollView` + /// + /// **default**: true + @objc open var isHighlightPerDragEnabled: Bool { + highlightPerDragEnabled + } + + /// **default**: true + /// `true` if drawing the grid background is enabled, `false` ifnot. + @objc open var isDrawGridBackgroundEnabled: Bool { + drawGridBackgroundEnabled + } + + /// **default**: false + /// `true` if drawing the borders rectangle is enabled, `false` ifnot. + @objc open var isDrawBordersEnabled: Bool { + drawBordersEnabled + } + + /// - Returns: The x and y values in the chart at the given touch point + /// (encapsulated in a `CGPoint`). This method transforms pixel coordinates to + /// coordinates / values in the chart. This is the opposite method to + /// `getPixelsForValues(...)`. + @objc open func valueForTouchPoint(point pt: CGPoint, axis: YAxis.AxisDependency) -> CGPoint { + getTransformer(forAxis: axis).valueForTouchPoint(pt) + } + + /// Transforms the given chart values into pixels. This is the opposite + /// method to `valueForTouchPoint(...)`. + @objc open func pixelForValues(x: Double, y: Double, axis: YAxis.AxisDependency) -> CGPoint { + getTransformer(forAxis: axis).pixelForValues(x: x, y: y) + } + + /// - Returns: The Entry object displayed at the touched position of the chart + @objc open func getEntryByTouchPoint(point pt: CGPoint) -> ChartDataEntry! { + if let h = getHighlightByTouchPoint(pt) { + return _data!.entryForHighlight(h) + } + return nil + } + + /// - Returns: The DataSet object displayed at the touched position of the chart + @objc open func getDataSetByTouchPoint(point pt: CGPoint) -> IBarLineScatterCandleBubbleChartDataSet? { + let h = getHighlightByTouchPoint(pt) + if h !== nil { + return _data?.getDataSetByIndex(h!.dataSetIndex) as? IBarLineScatterCandleBubbleChartDataSet + } + return nil + } + + /// The current x-scale factor + @objc open var scaleX: CGFloat { + if _viewPortHandler === nil { + return 1.0 + } + return _viewPortHandler.scaleX + } + + /// The current y-scale factor + @objc open var scaleY: CGFloat { + if _viewPortHandler === nil { + return 1.0 + } + return _viewPortHandler.scaleY + } + + /// if the chart is fully zoomed out, return true + @objc open var isFullyZoomedOut: Bool { _viewPortHandler.isFullyZoomedOut } + + /// - Returns: The y-axis object to the corresponding AxisDependency. In the + /// horizontal bar-chart, LEFT == top, RIGHT == BOTTOM + @objc open func getAxis(_ axis: YAxis.AxisDependency) -> YAxis { + if axis == .left { + return leftAxis + } else { + return rightAxis + } + } + + /// flag that indicates if pinch-zoom is enabled. if true, both x and y axis can be scaled simultaneously with 2 fingers, if false, x and y axis can be scaled separately + @objc open var pinchZoomEnabled: Bool { + get { + _pinchZoomEnabled + } + set { + if _pinchZoomEnabled != newValue { + _pinchZoomEnabled = newValue + #if !os(tvOS) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + } + } + + /// **default**: false + /// `true` if pinch-zoom is enabled, `false` ifnot + @objc open var isPinchZoomEnabled: Bool { pinchZoomEnabled } + + /// Set an offset in dp that allows the user to drag the chart over it's + /// bounds on the x-axis. + @objc open func setDragOffsetX(_ offset: CGFloat) { + _viewPortHandler.setDragOffsetX(offset) + } + + /// Set an offset in dp that allows the user to drag the chart over it's + /// bounds on the y-axis. + @objc open func setDragOffsetY(_ offset: CGFloat) { + _viewPortHandler.setDragOffsetY(offset) + } + + /// `true` if both drag offsets (x and y) are zero or smaller. + @objc open var hasNoDragOffset: Bool { _viewPortHandler.hasNoDragOffset } + + override open var chartYMax: Double { + max(leftAxis._axisMaximum, rightAxis._axisMaximum) + } + + override open var chartYMin: Double { + min(leftAxis._axisMinimum, rightAxis._axisMinimum) + } + + /// `true` if either the left or the right or both axes are inverted. + @objc open var isAnyAxisInverted: Bool { + leftAxis.isInverted || rightAxis.isInverted + } + + /// flag that indicates if auto scaling on the y axis is enabled. + /// if yes, the y axis automatically adjusts to the min and max y values of the current x axis range whenever the viewport changes + @objc open var autoScaleMinMaxEnabled: Bool { + get { _autoScaleMinMaxEnabled } + set { _autoScaleMinMaxEnabled = newValue } + } + + /// **default**: false + /// `true` if auto scaling on the y axis is enabled. + @objc open var isAutoScaleMinMaxEnabled: Bool { autoScaleMinMaxEnabled } + + /// Sets a minimum width to the specified y axis. + @objc open func setYAxisMinWidth(_ axis: YAxis.AxisDependency, width: CGFloat) { + if axis == .left { + leftAxis.minWidth = width + } else { + rightAxis.minWidth = width + } + } + + /// **default**: 0.0 + /// + /// - Returns: The (custom) minimum width of the specified Y axis. + @objc open func getYAxisMinWidth(_ axis: YAxis.AxisDependency) -> CGFloat { + if axis == .left { + return leftAxis.minWidth + } else { + return rightAxis.minWidth + } + } + + /// Sets a maximum width to the specified y axis. + /// Zero (0.0) means there's no maximum width + @objc open func setYAxisMaxWidth(_ axis: YAxis.AxisDependency, width: CGFloat) { + if axis == .left { + leftAxis.maxWidth = width + } else { + rightAxis.maxWidth = width + } + } + + /// Zero (0.0) means there's no maximum width + /// + /// **default**: 0.0 (no maximum specified) + /// + /// - Returns: The (custom) maximum width of the specified Y axis. + @objc open func getYAxisMaxWidth(_ axis: YAxis.AxisDependency) -> CGFloat { + if axis == .left { + return leftAxis.maxWidth + } else { + return rightAxis.maxWidth + } + } + + /// - Returns the width of the specified y axis. + @objc open func getYAxisWidth(_ axis: YAxis.AxisDependency) -> CGFloat { + if axis == .left { + return leftAxis.requiredSize().width + } else { + return rightAxis.requiredSize().width + } + } + + // MARK: - BarLineScatterCandleBubbleChartDataProvider + + /// - Returns: The Transformer class that contains all matrices and is + /// responsible for transforming values into pixels on the screen and + /// backwards. + open func getTransformer(forAxis axis: YAxis.AxisDependency) -> Transformer { + if axis == .left { + return _leftAxisTransformer + } else { + return _rightAxisTransformer + } + } + + /// the number of maximum visible drawn values on the chart only active when `drawValuesEnabled` is enabled + override open var maxVisibleCount: Int { + get { + _maxVisibleCount + } + set { + _maxVisibleCount = newValue + } + } + + open func isInverted(axis: YAxis.AxisDependency) -> Bool { + getAxis(axis).isInverted + } + + /// The lowest x-index (value on the x-axis) that is still visible on he chart. + open var lowestVisibleX: Double { + var pt = CGPoint( + x: viewPortHandler.contentLeft, + y: viewPortHandler.contentBottom + ) + + getTransformer(forAxis: .left).pixelToValues(&pt) + + return max(xAxis._axisMinimum, Double(pt.x)) + } + + /// The highest x-index (value on the x-axis) that is still visible on the chart. + open var highestVisibleX: Double { + var pt = CGPoint( + x: viewPortHandler.contentRight, + y: viewPortHandler.contentBottom + ) + + getTransformer(forAxis: .left).pixelToValues(&pt) + + return min(xAxis._axisMaximum, Double(pt.x)) + } +} diff --git a/Pods/Charts/Source/Charts/Charts/BubbleChartView.swift b/Pods/Charts/Source/Charts/Charts/BubbleChartView.swift new file mode 100644 index 0000000..c78343b --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/BubbleChartView.swift @@ -0,0 +1,25 @@ +// +// BubbleChartView.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import CoreGraphics +import Foundation + +open class BubbleChartView: BarLineChartViewBase, BubbleChartDataProvider { + override open func initialize() { + super.initialize() + + renderer = BubbleChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + } + + // MARK: - BubbleChartDataProvider + + open var bubbleData: BubbleChartData? { _data as? BubbleChartData } +} diff --git a/Pods/Charts/Source/Charts/Charts/CandleStickChartView.swift b/Pods/Charts/Source/Charts/Charts/CandleStickChartView.swift new file mode 100644 index 0000000..0fff91c --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/CandleStickChartView.swift @@ -0,0 +1,31 @@ +// +// 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 CoreGraphics +import Foundation + +/// Financial chart type that draws candle-sticks. +open class CandleStickChartView: BarLineChartViewBase, CandleChartDataProvider { + override internal func initialize() { + super.initialize() + + renderer = CandleStickChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + + xAxis.spaceMin = 0.5 + xAxis.spaceMax = 0.5 + } + + // MARK: - CandleChartDataProvider + + open var candleData: CandleChartData? { + _data as? CandleChartData + } +} diff --git a/Pods/Charts/Source/Charts/Charts/ChartViewBase.swift b/Pods/Charts/Source/Charts/Charts/ChartViewBase.swift new file mode 100644 index 0000000..208aaf5 --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/ChartViewBase.swift @@ -0,0 +1,917 @@ +// +// 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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +@objc +public protocol ChartViewDelegate { + /// Called when a value has been selected inside the chart. + /// + /// - Parameters: + /// - entry: The selected Entry. + /// - 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 a user stops panning between values on the chart + @objc optional func chartViewDidEndPanning(_ chartView: ChartViewBase) + + // 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) + + // Callbacks when Animator stops animating + @objc optional func chartView(_ chartView: ChartViewBase, animatorDidStop animator: Animator) +} + +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 { + _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.systemFont(ofSize: 12) + + /// color of the no data text + @objc open var noDataTextColor: NSUIColor = .labelOrBlack + + /// alignment of the no data text + @objc 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 { 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 + + override public 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) + 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() + + addObserver(self, forKeyPath: "bounds", options: .new, context: nil) + addObserver(self, forKeyPath: "frame", options: .new, context: nil) + } + + // MARK: - ChartViewBase + + /// The data for the chart + open var data: ChartData? { + get { + _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 + } + } + + override open func draw(_: CGRect) { + let optionalContext = NSUIGraphicsGetCurrentContext() + guard let context = optionalContext else { return } + + let frame = 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: 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 = [NSAttributedString.Key: Any]() + + attrs[NSAttributedString.Key.font] = description.font + attrs[NSAttributedString.Key.foregroundColor] = description.textColor + + ChartUtils.drawText( + context: context, + text: descriptionText, + point: position, + align: description.textAlign, + attributes: attrs + ) + } + + // MARK: - Accessibility + + override open func accessibilityChildren() -> [Any]? { + renderer?.accessibleChartElements + } + + // MARK: - Highlighting + + /// The array of currently highlighted values. This might an empty if nothing is highlighted. + @objc open var highlighted: [Highlight] { + _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 { _highlightPerTapEnabled } + set { _highlightPerTapEnabled = newValue } + } + + /// `true` if values can be highlighted via tap gesture, `false` ifnot. + @objc open var isHighLightPerTapEnabled: Bool { + 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 { + !_indicesToHighlight.isEmpty + } + + /// 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 { + lastHighlighted = nil + } else { + 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. + /// + /// - Parameters: + /// - x: The x-value to highlight + /// - dataSetIndex: The dataset index to search in + /// - 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. + /// + /// - Parameters: + /// - x: The x-value to highlight + /// - y: The y-value to highlight. Supply `NaN` for "any" + /// - dataSetIndex: The dataset index to search in + /// - 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. + /// + /// - Parameters: + /// - x: The x-value to highlight + /// - dataSetIndex: The dataset index to search in + /// - dataIndex: The data index to search in (only used in CombinedChartView currently) + /// - 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. + /// + /// - Parameters: + /// - x: The x-value to highlight + /// - y: The y-value to highlight. Supply `NaN` for "any" + /// - dataSetIndex: The dataset index to search in + /// - dataIndex: The data index to search in (only used in CombinedChartView currently) + /// - 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. + /// + /// - Parameters: + /// - 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 { + 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 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 { + CGPoint(x: highlight.drawX, y: highlight.drawY) + } + + // MARK: - Animation + + /// The animator responsible for animating chart values. + @objc open var chartAnimator: Animator! { + _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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingX: an easing function for the animation on the x axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingOptionX: the easing function for the animation on the x axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - 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. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - 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. + /// + /// - Parameters: + /// - 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. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + /// - 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. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + /// - 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. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + @objc open func animate(yAxisDuration: TimeInterval) { + _animator.animate(yAxisDuration: yAxisDuration) + } + + // MARK: - Accessors + + /// The current y-max value across all DataSets + open var chartYMax: Double { + _data?.yMax ?? 0.0 + } + + /// The current y-min value across all DataSets + open var chartYMin: Double { + _data?.yMin ?? 0.0 + } + + open var chartXMax: Double { + _xAxis._axisMaximum + } + + open var chartXMin: Double { + _xAxis._axisMinimum + } + + open var xRange: Double { + _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)* + /// 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) + } + + /// The center of the chart taking offsets under consideration. (returns the center of the content rectangle) + open var centerOffsets: CGPoint { + _viewPortHandler.contentCenter + } + + /// 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 { + _legend + } + + /// The renderer object responsible for rendering / drawing the Legend. + @objc open var legendRenderer: LegendRenderer! { + _legendRenderer + } + + /// The rectangle that defines the borders of the chart-value surface (into which the actual values are drawn). + @objc open var contentRect: CGRect { + _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! { + _viewPortHandler + } + + /// - Returns: The bitmap that represents the chart. + @objc open func getChartImage(transparent: Bool) -> NSUIImage? { + NSUIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque || !transparent, NSUIScreen.nsuiMain?.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") + /// + /// - Parameters: + /// - to: path to the image to save + /// - format: the format to save + /// - 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]() + + override open func observeValue(forKeyPath keyPath: String?, of _: 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.firstIndex(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 + /// `true` if chart continues to scroll after touch up, `false` ifnot. + @objc open var isDragDecelerationEnabled: Bool { + 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 { + _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 { + Int(INT_MAX) + } + + // MARK: - AnimatorDelegate + + open func animatorUpdated(_: Animator) { + setNeedsDisplay() + } + + open func animatorStopped(_ chartAnimator: Animator) { + delegate?.chartView?(self, animatorDidStop: chartAnimator) + } + + // MARK: - Touches + + override open func nsuiTouchesBegan(_ touches: Set, withEvent event: NSUIEvent?) { + if !_interceptTouchEvents { + super.nsuiTouchesBegan(touches, withEvent: event) + } + } + + override open func nsuiTouchesMoved(_ touches: Set, withEvent event: NSUIEvent?) { + if !_interceptTouchEvents { + super.nsuiTouchesMoved(touches, withEvent: event) + } + } + + override open func nsuiTouchesEnded(_ touches: Set, withEvent event: NSUIEvent?) { + if !_interceptTouchEvents { + super.nsuiTouchesEnded(touches, withEvent: event) + } + } + + override open func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) { + if !_interceptTouchEvents { + super.nsuiTouchesCancelled(touches, withEvent: event) + } + } +} diff --git a/Pods/Charts/Source/Charts/Charts/CombinedChartView.swift b/Pods/Charts/Source/Charts/Charts/CombinedChartView.swift new file mode 100644 index 0000000..483edd2 --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/CombinedChartView.swift @@ -0,0 +1,202 @@ +// +// 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 CoreGraphics +import Foundation + +/// 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 + } + + override open func initialize() { + super.initialize() + + highlighter = CombinedHighlighter(chart: self, barDataProvider: self) + + // Old default behaviour + highlightFullBarEnabled = true + + _fillFormatter = DefaultFillFormatter() + + renderer = CombinedChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler) + } + + override open var data: ChartData? { + get { + super.data + } + set { + super.data = newValue + + highlighter = CombinedHighlighter(chart: self, barDataProvider: self) + + (renderer as? CombinedChartRenderer)?.createRenderers() + renderer?.initBuffers() + } + } + + @objc open var fillFormatter: IFillFormatter { + get { + _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. + override open func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight? { + if _data === nil { + Swift.print("Can't select by touch. No data set.") + return nil + } + + guard let h = 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? { + _data as? CombinedChartData + } + + // MARK: - LineChartDataProvider + + open var lineData: LineChartData? { + combinedData?.lineData + } + + // MARK: - BarChartDataProvider + + open var barData: BarChartData? { + combinedData?.barData + } + + // MARK: - ScatterChartDataProvider + + open var scatterData: ScatterChartData? { + combinedData?.scatterData + } + + // MARK: - CandleChartDataProvider + + open var candleData: CandleChartData? { + combinedData?.candleData + } + + // MARK: - BubbleChartDataProvider + + open var bubbleData: BubbleChartData? { + 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 { (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 { (renderer as! CombinedChartRenderer).drawBarShadowEnabled } + set { (renderer as! CombinedChartRenderer).drawBarShadowEnabled = newValue } + } + + /// `true` if drawing values above bars is enabled, `false` ifnot + open var isDrawValueAboveBarEnabled: Bool { (renderer as! CombinedChartRenderer).drawValueAboveBarEnabled } + + /// `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot + open var isDrawBarShadowEnabled: Bool { (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 { + (renderer as! CombinedChartRenderer).drawOrder.map(\.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 + + /// `true` the highlight is be full-bar oriented, `false` ifsingle-value + open var isHighlightFullBarEnabled: Bool { 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) + } + } +} diff --git a/Pods/Charts/Source/Charts/Charts/HorizontalBarChartView.swift b/Pods/Charts/Source/Charts/Charts/HorizontalBarChartView.swift new file mode 100644 index 0000000..ff8619e --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/HorizontalBarChartView.swift @@ -0,0 +1,241 @@ +// +// 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 CoreGraphics +import Foundation + +/// BarChart with horizontal bar orientation. In this implementation, x- and y-axis are switched. +open class HorizontalBarChartView: BarChartView { + override internal 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) + + highlighter = HorizontalBarHighlighter(chart: self) + } + + override internal 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 + } + } + } + + override internal 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 += extraTopOffset + offsetRight += extraRightOffset + offsetBottom += extraBottomOffset + offsetLeft += extraLeftOffset + + _viewPortHandler.restrainViewPort( + offsetLeft: max(minOffset, offsetLeft), + offsetTop: max(minOffset, offsetTop), + offsetRight: max(minOffset, offsetRight), + offsetBottom: max(minOffset, offsetBottom) + ) + + prepareOffsetMatrix() + prepareValuePxMatrix() + } + + override internal 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) + } + + override open func getMarkerPosition(highlight: Highlight) -> CGPoint { + CGPoint(x: highlight.drawY, y: highlight.drawX) + } + + override 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 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 + } + + override open 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 + } + + override open func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight? { + if _data === nil { + Swift.print("Can't select by touch. No data set.", terminator: "\n") + return nil + } + + return highlighter?.getHighlight(x: pt.y, y: pt.x) + } + + /// The lowest x-index (value on the x-axis) that is still visible on he chart. + override open var lowestVisibleX: Double { + var pt = CGPoint( + x: viewPortHandler.contentLeft, + y: viewPortHandler.contentBottom + ) + + getTransformer(forAxis: .left).pixelToValues(&pt) + + return max(xAxis._axisMinimum, Double(pt.y)) + } + + /// The highest x-index (value on the x-axis) that is still visible on the chart. + override open 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 + + override open func setVisibleXRangeMaximum(_ maxXRange: Double) { + let xScale = xAxis.axisRange / maxXRange + viewPortHandler.setMinimumScaleY(CGFloat(xScale)) + } + + override open func setVisibleXRangeMinimum(_ minXRange: Double) { + let xScale = xAxis.axisRange / minXRange + viewPortHandler.setMaximumScaleY(CGFloat(xScale)) + } + + override open func setVisibleXRange(minXRange: Double, maxXRange: Double) { + let minScale = xAxis.axisRange / minXRange + let maxScale = xAxis.axisRange / maxXRange + viewPortHandler.setMinMaxScaleY(minScaleY: CGFloat(minScale), maxScaleY: CGFloat(maxScale)) + } + + override open func setVisibleYRangeMaximum(_ maxYRange: Double, axis: YAxis.AxisDependency) { + let yScale = getAxisRange(axis: axis) / maxYRange + viewPortHandler.setMinimumScaleX(CGFloat(yScale)) + } + + override open func setVisibleYRangeMinimum(_ minYRange: Double, axis: YAxis.AxisDependency) { + let yScale = getAxisRange(axis: axis) / minYRange + viewPortHandler.setMaximumScaleX(CGFloat(yScale)) + } + + override open 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)) + } +} diff --git a/Pods/Charts/Source/Charts/Charts/LineChartView.swift b/Pods/Charts/Source/Charts/Charts/LineChartView.swift new file mode 100644 index 0000000..e7f2d4c --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/LineChartView.swift @@ -0,0 +1,26 @@ +// +// 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 CoreGraphics +import Foundation + +/// Chart that draws lines, surfaces, circles, ... +open class LineChartView: BarLineChartViewBase, LineChartDataProvider { + override internal func initialize() { + super.initialize() + + renderer = LineChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + } + + // MARK: - LineChartDataProvider + + open var lineData: LineChartData? { _data as? LineChartData } +} diff --git a/Pods/Charts/Source/Charts/Charts/PieChartView.swift b/Pods/Charts/Source/Charts/Charts/PieChartView.swift new file mode 100644 index 0000000..2a95b52 --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/PieChartView.swift @@ -0,0 +1,576 @@ +// +// 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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#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() + + /// 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 + + override public init(frame: CGRect) { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + override internal func initialize() { + super.initialize() + + renderer = PieChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler) + _xAxis = nil + + highlighter = PieHighlighter(chart: self) + } + + override open 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) + } + + /// if width is larger than height + private var widthLarger: Bool { + _viewPortHandler.contentRect.orientation == .landscape + } + + /// adjusted radius. Use diameter when it's half pie and width is larger + private var adjustedRadius: CGFloat { + maxAngle <= 180 && widthLarger ? diameter : diameter / 2.0 + } + + /// true centerOffsets considering half pie & width is larger + private func adjustedCenterOffsets() -> CGPoint { + var c = centerOffsets + c.y = maxAngle <= 180 && widthLarger ? c.y + adjustedRadius / 2 : c.y + return c + } + + override internal func calculateOffsets() { + super.calculateOffsets() + + // prevent nullpointer when no data set + if _data === nil { + return + } + + let radius = adjustedRadius + + let c = adjustedCenterOffsets() + + 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 = radius * 2 - shift * 2.0 + _circleBox.size.height = radius * 2 - shift * 2.0 + } + + override internal func calcMinMax() { + calcAngles() + } + + @objc override open func angleForPoint(x: CGFloat, y: CGFloat) -> CGFloat { + let c = adjustedCenterOffsets() + + 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) + } + + /// - Returns: The distance of a certain point on the chart to the center of the chart. + @objc override open func distanceToCenter(x: CGFloat, y: CGFloat) -> CGFloat { + let c = adjustedCenterOffsets() + + 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 + } + + override open func getMarkerPosition(highlight: Highlight) -> CGPoint { + let center = centerCircleBox + var r = radius + + var off = r / 10.0 * 3.6 + + if isDrawHoleEnabled { + off = (r - (r * 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 cnt = 0 + + for set in data.dataSets { + for j in 0 ..< set.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 { + _indicesToHighlight.contains { Int($0.x) == index } + } + + /// calculates the needed angle for a given value + private func calcAngle(_ value: Double) -> CGFloat { + calcAngle(value: value, yValueSum: (_data as! PieChartData).yValueSum) + } + + /// calculates the needed angle for a given value + private func calcAngle(value: Double, yValueSum: Double) -> CGFloat { + CGFloat(value) / CGFloat(yValueSum) * _maxAngle + } + + /// This will throw an exception, PieChart has no XAxis object. + override open var xAxis: XAxis { + fatalError("PieChart has no XAxis") + } + + override open func indexForAngle(_ angle: CGFloat) -> Int { + // TODO: Return nil instead of -1 + // take the current angle of the chart into consideration + let a = (angle - rotationAngle).normalizedAngle + return _absoluteAngles.firstIndex { $0 > a } ?? -1 + } + + /// - Returns: The index of the DataSet this x-index belongs to. + @objc open func dataSetIndexForIndex(_ xValue: Double) -> Int { + // TODO: Return nil instead of -1 + _data?.dataSets.firstIndex { + $0.entryForXValue(xValue, closestToY: .nan) != nil + } ?? -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] { + _drawAngles + } + + /// - Returns: The absolute angles of the different chart slices (where the + /// slices end) + @objc open var absoluteAngles: [CGFloat] { + _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 { + _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 { + _drawSlicesUnderHoleEnabled + } + set { + _drawSlicesUnderHoleEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if the inner tips of the slices are visible behind the hole, `false` if not. + @objc open var isDrawSlicesUnderHoleEnabled: Bool { + 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 { + _drawHoleEnabled + } + set { + _drawHoleEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if the hole in the center of the pie-chart is set to be visible, `false` ifnot + @objc open var isDrawHoleEnabled: Bool { + drawHoleEnabled + } + + /// the text that is displayed in the center of the pie-chart + @objc open var centerText: String? { + get { + centerAttributedText?.string + } + set { + var attrString: NSMutableAttributedString? + if newValue == nil { + attrString = nil + } else { + let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + paragraphStyle.lineBreakMode = .byTruncatingTail + paragraphStyle.alignment = .center + + attrString = NSMutableAttributedString(string: newValue!) + attrString?.setAttributes([ + .foregroundColor: NSUIColor.labelOrBlack, + .font: NSUIFont.systemFont(ofSize: 12.0), + .paragraphStyle: paragraphStyle, + ], range: NSMakeRange(0, attrString!.length)) + } + centerAttributedText = attrString + } + } + + /// the text that is displayed in the center of the pie-chart + @objc open var centerAttributedText: NSAttributedString? { + get { + _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 { + _centerTextOffset + } + set { + _centerTextOffset = newValue + setNeedsDisplay() + } + } + + /// `true` if drawing the center text is enabled + @objc open var drawCenterTextEnabled: Bool { + get { + _drawCenterTextEnabled + } + set { + _drawCenterTextEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if drawing the center text is enabled + @objc open var isDrawCenterTextEnabled: Bool { + drawCenterTextEnabled + } + + override internal var requiredLegendOffset: CGFloat { + _legend.font.pointSize * 2.0 + } + + override internal var requiredBaseOffset: CGFloat { + 0.0 + } + + override open var radius: CGFloat { + _circleBox.width / 2.0 + } + + /// The circlebox, the boundingbox of the pie-chart slices + @objc open var circleBox: CGRect { + _circleBox + } + + /// The center of the circlebox + @objc open var centerCircleBox: CGPoint { + 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 { + _holeRadiusPercent + } + set { + _holeRadiusPercent = newValue + setNeedsDisplay() + } + } + + /// The color that the transparent-circle should have. + /// + /// **default**: `nil` + @objc open var transparentCircleColor: NSUIColor? { + get { + _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 { + _transparentCircleRadiusPercent + } + set { + _transparentCircleRadiusPercent = newValue + setNeedsDisplay() + } + } + + /// The color the entry labels are drawn with. + @objc open var entryLabelColor: NSUIColor? { + get { _entryLabelColor } + set { + _entryLabelColor = newValue + setNeedsDisplay() + } + } + + /// The font the entry labels are drawn with. + @objc open var entryLabelFont: NSUIFont? { + get { _entryLabelFont } + set { + _entryLabelFont = newValue + setNeedsDisplay() + } + } + + /// Set this to true to draw the enrty labels into the pie slices + @objc open var drawEntryLabelsEnabled: Bool { + get { + _drawEntryLabelsEnabled + } + set { + _drawEntryLabelsEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if drawing entry labels is enabled, `false` ifnot + @objc open var isDrawEntryLabelsEnabled: Bool { + 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 { + _usePercentValuesEnabled + } + set { + _usePercentValuesEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if drawing x-values is enabled, `false` ifnot + @objc open var isUsePercentValuesEnabled: Bool { + 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 { + _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 { + _maxAngle + } + set { + _maxAngle = newValue + + if _maxAngle > 360.0 { + _maxAngle = 360.0 + } + + if _maxAngle < 90.0 { + _maxAngle = 90.0 + } + } + } +} diff --git a/Pods/Charts/Source/Charts/Charts/PieRadarChartViewBase.swift b/Pods/Charts/Source/Charts/Charts/PieRadarChartViewBase.swift new file mode 100644 index 0000000..9feb155 --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/PieRadarChartViewBase.swift @@ -0,0 +1,738 @@ +// +// 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 CoreGraphics +import Foundation +import QuartzCore + +#if canImport(AppKit) + import AppKit +#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 + + override public init(frame: CGRect) { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + deinit { + stopDeceleration() + } + + override internal func initialize() { + super.initialize() + + _tapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))) + + addGestureRecognizer(_tapGestureRecognizer) + + #if !os(tvOS) + _rotationGestureRecognizer = NSUIRotationGestureRecognizer(target: self, action: #selector(rotationGestureRecognized(_:))) + addGestureRecognizer(_rotationGestureRecognizer) + _rotationGestureRecognizer.isEnabled = rotationWithTwoFingers + #endif + } + + override internal func calcMinMax() { + /* _xAxis.axisRange = Double((_data?.xVals.count ?? 0) - 1) */ + } + + override open var maxVisibleCount: Int { + data?.entryCount ?? 0 + } + + override open func notifyDataSetChanged() { + calcMinMax() + + if let data = _data, _legend !== nil { + legendRenderer.computeLegend(data: data) + } + + calculateOffsets() + + setNeedsDisplay() + } + + override internal 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 = midPoint + + let bottomX = _legend.horizontalAlignment == .right + ? 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: 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, + bounds.height - legendWidth > 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 = requiredLegendOffset + + yLegendOffset = min( + _legend.neededHeight + yOffset, + _viewPortHandler.chartHeight * _legend.maxSizePercent + ) + } + + switch _legend.verticalAlignment { + case .top: + + legendTop = yLegendOffset + + case .bottom: + + legendBottom = yLegendOffset + + default: + break + } + } + + legendLeft += requiredBaseOffset + legendRight += requiredBaseOffset + legendTop += requiredBaseOffset + legendBottom += requiredBaseOffset + } + + legendTop += extraTopOffset + legendRight += extraRightOffset + legendBottom += extraBottomOffset + legendLeft += extraLeftOffset + + var minOffset = self.minOffset + + if self is RadarChartView { + let x = 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(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 { + 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 = 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(_: CGFloat) -> Int { + fatalError("indexForAngle() cannot be called on PieRadarChartViewBase") + } + + /// current rotation angle of the pie chart + /// + /// **default**: 270 --> top (NORTH) + /// Will always return a normalized value, which will be between 0.0 < 360.0 + @objc open var rotationAngle: CGFloat { + get { + _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 { + _rawRotationAngle + } + + /// 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) + } + + /// The radius of the chart in pixels. + @objc open var radius: CGFloat { + fatalError("radius cannot be called on PieRadarChartViewBase") + } + + /// 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") + } + + override open var chartYMax: Double { + 0.0 + } + + override open var chartYMin: Double { + 0.0 + } + + @objc open var isRotationEnabled: Bool { 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 { + _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 { + _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) { + resetVelocity() + + if rotationEnabled { + sampleVelocity(touchLocation: location) + } + + 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 { + 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: RunLoop.Mode.common) + } + } + } + + internal final func processRotationGestureCancelled() { + if _isRotating { + _isRotating = false + } + } + + #if !os(OSX) + override open func nsuiTouchesBegan(_ touches: Set, 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) + } + } + + override open func nsuiTouchesMoved(_ touches: Set, 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) + } + } + + override open func nsuiTouchesEnded(_ touches: Set, 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 + } + } + + override open func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) { + super.nsuiTouchesCancelled(touches, withEvent: event) + + processRotationGestureCancelled() + } + #endif + + #if os(OSX) + override open func mouseDown(with theEvent: NSEvent) { + // if rotation by touch is enabled + if rotationEnabled { + stopDeceleration() + + let location = convert(theEvent.locationInWindow, from: nil) + + processRotationGestureBegan(location: location) + } + + if !_isRotating { + super.mouseDown(with: theEvent) + } + } + + override open func mouseDragged(with theEvent: NSEvent) { + if rotationEnabled { + let location = convert(theEvent.locationInWindow, from: nil) + + processRotationGestureMoved(location: location) + } + + if !_isRotating { + super.mouseDragged(with: theEvent) + } + } + + override open func mouseUp(with theEvent: NSEvent) { + if !_isRotating { + super.mouseUp(with: theEvent) + } + + if rotationEnabled { + let location = 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 currentSample: AngularVelocitySample = { + let time = CACurrentMediaTime() + let angle = angleForPoint(x: touchLocation.x, y: touchLocation.y) + return AngularVelocitySample(time: time, angle: angle) + }() + + // Remove samples older than our sample time - 1 seconds + // while keeping at least one sample + + var i = 0, count = velocitySamples.count + while i < count - 2 { + if currentSample.time - velocitySamples[i].time > 1.0 { + velocitySamples.remove(at: 0) + i -= 1 + count -= 1 + } else { + break + } + + i += 1 + } + + velocitySamples.append(currentSample) + } + + private func calculateVelocity() -> CGFloat { + guard var firstSample = velocitySamples.first, + var lastSample = velocitySamples.last + else { return 0 } + + // Look for a sample that's closest to the latest sample, but not the same, so we can deduce the direction + let beforeLastSample = velocitySamples.last { $0.angle != lastSample.angle } + ?? firstSample + + // Calculate the sampling time + let timeDelta: CGFloat = { + let delta = CGFloat(lastSample.time - firstSample.time) + return delta == 0 ? 0.1 : delta + }() + + // 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" + let isClockwise: Bool = { + let isClockwise = lastSample.angle >= beforeLastSample.angle + let isInverted = abs(lastSample.angle - beforeLastSample.angle) > 270.0 + return isInverted ? !isClockwise : isClockwise + }() + + // 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 + let velocity = abs((lastSample.angle - firstSample.angle) / timeDelta) + return isClockwise ? velocity : -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) { + rotationAngle = angleForPoint(x: x, y: y) - _startAngle + } + + @objc open func stopDeceleration() { + if _decelerationDisplayLink !== nil { + _decelerationDisplayLink.remove(from: RunLoop.main, forMode: RunLoop.Mode.common) + _decelerationDisplayLink = nil + } + } + + @objc private func decelerationLoop() { + let currentTime = CACurrentMediaTime() + + _decelerationAngularVelocity *= dragDecelerationFrictionCoef + + let timeInterval = CGFloat(currentTime - _decelerationLastTime) + + 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 !isHighLightPerTapEnabled { return } + + let location = recognizer.location(in: self) + + let high = getHighlightByTouchPoint(location) + highlightValue(high, callDelegate: true) + } + } + + #if !os(tvOS) + @objc private func rotationGestureRecognized(_ recognizer: NSUIRotationGestureRecognizer) { + if recognizer.state == NSUIGestureRecognizerState.began { + stopDeceleration() + + _startAngle = rawRotationAngle + } + + if recognizer.state == NSUIGestureRecognizerState.began || recognizer.state == NSUIGestureRecognizerState.changed { + let angle = recognizer.nsuiRotation.RAD2DEG + + rotationAngle = _startAngle + angle + setNeedsDisplay() + } else if recognizer.state == NSUIGestureRecognizerState.ended { + let angle = recognizer.nsuiRotation.RAD2DEG + + 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: RunLoop.Mode.common) + } + } + } + } + #endif +} diff --git a/Pods/Charts/Source/Charts/Charts/RadarChartView.swift b/Pods/Charts/Source/Charts/Charts/RadarChartView.swift new file mode 100644 index 0000000..54ca5c0 --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/RadarChartView.swift @@ -0,0 +1,198 @@ +// +// 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 CoreGraphics +import Foundation + +/// 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! + + override public init(frame: CGRect) { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + override internal func initialize() { + super.initialize() + + _yAxis = YAxis(position: .left) + _yAxis.labelXOffset = 10.0 + + renderer = RadarChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler) + + _yAxisRenderer = YAxisRendererRadarChart(viewPortHandler: _viewPortHandler, yAxis: _yAxis, chart: self) + _xAxisRenderer = XAxisRendererRadarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, chart: self) + + highlighter = RadarHighlighter(chart: self) + } + + override internal 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)) + } + + override open 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() + } + + override open 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) + } + + /// 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) + } + + /// The angle that each slice in the radar chart occupies. + @objc open var sliceAngle: CGFloat { + 360.0 / CGFloat(_data?.maxEntryCountSet?.entryCount ?? 0) + } + + override open func indexForAngle(_ angle: CGFloat) -> Int { + // take the current angle of the chart into consideration + let a = (angle - rotationAngle).normalizedAngle + + let sliceAngle = self.sliceAngle + + let max = _data?.maxEntryCountSet?.entryCount ?? 0 + return (0 ..< max).firstIndex { + sliceAngle * CGFloat($0 + 1) - sliceAngle / 2.0 > a + } ?? max + } + + /// The object that represents all y-labels of the RadarChart. + @objc open var yAxis: YAxis { + _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 { + _skipWebLineCount + } + set { + _skipWebLineCount = max(0, newValue) + } + } + + override internal var requiredLegendOffset: CGFloat { + _legend.font.pointSize * 4.0 + } + + override internal var requiredBaseOffset: CGFloat { + _xAxis.isEnabled && _xAxis.isDrawLabelsEnabled ? _xAxis.labelRotatedWidth : 10.0 + } + + override open var radius: CGFloat { + let content = _viewPortHandler.contentRect + return min(content.width / 2.0, content.height / 2.0) + } + + /// The maximum value this chart can display on it's y-axis. + override open var chartYMax: Double { _yAxis._axisMaximum } + + /// The minimum value this chart can display on it's y-axis. + override open var chartYMin: Double { _yAxis._axisMinimum } + + /// The range of y-values this chart can display. + @objc open var yRange: Double { _yAxis.axisRange } +} diff --git a/Pods/Charts/Source/Charts/Charts/ScatterChartView.swift b/Pods/Charts/Source/Charts/Charts/ScatterChartView.swift new file mode 100644 index 0000000..ed6813b --- /dev/null +++ b/Pods/Charts/Source/Charts/Charts/ScatterChartView.swift @@ -0,0 +1,29 @@ +// +// 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 CoreGraphics +import Foundation + +/// The ScatterChart. Draws dots, triangles, squares and custom shapes into the chartview. +open class ScatterChartView: BarLineChartViewBase, ScatterChartDataProvider { + override open 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? { _data as? ScatterChartData } +} diff --git a/Pods/Charts/Source/Charts/Components/AxisBase.swift b/Pods/Charts/Source/Charts/Components/AxisBase.swift new file mode 100644 index 0000000..a7161b0 --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/AxisBase.swift @@ -0,0 +1,331 @@ +// +// 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 CoreGraphics +import Foundation + +/// Base class for all axes +@objc(ChartAxisBase) +open class AxisBase: ComponentBase { + override public 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.labelOrBlack + + @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 { _centerAxisLabelsEnabled && entryCount > 0 } + set { _centerAxisLabelsEnabled = newValue } + } + + @objc open var isCenterAxisLabelsEnabled: Bool { 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 + + /// Are the grid lines drawn behind the data or in front of the data? + /// + /// **default**: true + @objc open var drawGridLinesBehindDataEnabled = true + + /// 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 { 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 { + _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 { + 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 = DefaultAxisValueFormatter(decimals: decimals) + } else if _axisValueFormatter is DefaultAxisValueFormatter, + (_axisValueFormatter as! DefaultAxisValueFormatter).hasAutoDecimals, + (_axisValueFormatter as! DefaultAxisValueFormatter).decimals != decimals + { + (_axisValueFormatter as! DefaultAxisValueFormatter).decimals = decimals + } + + return _axisValueFormatter + } + set { + _axisValueFormatter = newValue ?? DefaultAxisValueFormatter(decimals: decimals) + } + } + + @objc open var isDrawGridLinesEnabled: Bool { drawGridLinesEnabled } + + @objc open var isDrawAxisLineEnabled: Bool { drawAxisLineEnabled } + + @objc open var isDrawLabelsEnabled: Bool { drawLabelsEnabled } + + /// Are the LimitLines drawn behind the data or in front of the data? + /// + /// **default**: false + @objc open var isDrawLimitLinesBehindDataEnabled: Bool { drawLimitLinesBehindDataEnabled } + + /// Are the grid lines drawn behind the data or in front of the data? + /// + /// **default**: true + @objc open var isDrawGridLinesBehindDataEnabled: Bool { drawGridLinesBehindDataEnabled } + + /// 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 { axisMaxLabels = 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 { + _labelCount + } + set { + let range = axisMinLabels ... axisMaxLabels as ClosedRange + _labelCount = newValue.clamped(to: range) + + forceLabelsEnabled = false + } + } + + @objc open func setLabelCount(_ count: Int, force: Bool) { + labelCount = count + forceLabelsEnabled = force + } + + /// `true` if focing the y-label count is enabled. Default: false + @objc open var isForceLabelsEnabled: Bool { 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) { + guard let i = _limitLines.firstIndex(of: line) else { return } + _limitLines.remove(at: i) + } + + /// Removes all LimitLines from the axis. + @objc open func removeAllLimitLines() { + _limitLines.removeAll(keepingCapacity: false) + } + + /// The LimitLines of this axis. + @objc open var limitLines: [ChartLimitLine] { + _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 { _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 { _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 { + _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 { + _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. + /// + /// - Parameters: + /// - dataMin: the y-min value according to chart data + /// - 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) + } +} diff --git a/Pods/Charts/Source/Charts/Components/ChartLimitLine.swift b/Pods/Charts/Source/Charts/Components/ChartLimitLine.swift new file mode 100644 index 0000000..dc7b47c --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/ChartLimitLine.swift @@ -0,0 +1,65 @@ +// +// 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 CoreGraphics +import Foundation + +/// 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 topLeft + case topRight + case bottomLeft + case bottomRight + } + + /// 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.labelOrBlack + @objc open var valueFont = NSUIFont.systemFont(ofSize: 13.0) + + @objc open var drawLabelEnabled = true + @objc open var label = "" + @objc open var labelPosition = LabelPosition.topRight + + override public 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 { + _lineWidth + } + set { + _lineWidth = newValue.clamped(to: 0.2 ... 12) + } + } +} diff --git a/Pods/Charts/Source/Charts/Components/ComponentBase.swift b/Pods/Charts/Source/Charts/Components/ComponentBase.swift new file mode 100644 index 0000000..eb02ed1 --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/ComponentBase.swift @@ -0,0 +1,34 @@ +// +// 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 CoreGraphics +import Foundation + +/// 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) + + override public init() { + super.init() + } + + @objc open var isEnabled: Bool { enabled } +} diff --git a/Pods/Charts/Source/Charts/Components/Description.swift b/Pods/Charts/Source/Charts/Components/Description.swift new file mode 100644 index 0000000..97f1257 --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/Description.swift @@ -0,0 +1,52 @@ +// +// 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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +@objc(ChartDescription) +open class Description: ComponentBase { + override public init() { + #if os(tvOS) + // 23 is the smallest recommended font size on the TV + font = .systemFont(ofSize: 23) + #elseif os(OSX) + font = .systemFont(ofSize: NSUIFont.systemFontSize) + #else + font = .systemFont(ofSize: 8.0) + #endif + + super.init() + } + + /// The text to be shown as the description. + @objc open var text: String? + + /// Custom position for the description text in pixels on the screen. + open var position: CGPoint? + + /// The text alignment of the description text. Default RIGHT. + @objc open var textAlign = 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.labelOrBlack +} diff --git a/Pods/Charts/Source/Charts/Components/IMarker.swift b/Pods/Charts/Source/Charts/Components/IMarker.swift new file mode 100644 index 0000000..d18ea40 --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/IMarker.swift @@ -0,0 +1,38 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(IChartMarker) +public protocol IMarker: AnyObject { + /// - 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 } + + /// - Parameters: + /// - point: This is the point at which the marker wants to be drawn. You can adjust the offset conditionally based on this argument. + /// - 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(). + 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. + /// + /// - Parameters: + /// - entry: The Entry the IMarker belongs to. This can also be any subclass of Entry, like BarEntry or CandleEntry, simply cast it at runtime. + /// - 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) +} diff --git a/Pods/Charts/Source/Charts/Components/Legend.swift b/Pods/Charts/Source/Charts/Components/Legend.swift new file mode 100644 index 0000000..d6ffd6c --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/Legend.swift @@ -0,0 +1,378 @@ +// +// 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 CoreGraphics +import Foundation + +@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.left + + /// The vertical alignment of the legend + @objc open var verticalAlignment = VerticalAlignment.bottom + + /// The orientation of the legend + @objc open var 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 { drawInside } + + /// The text direction of the legend + @objc open var direction = Direction.leftToRight + + @objc open var font = NSUIFont.systemFont(ofSize: 10.0) + @objc open var textColor = NSUIColor.labelOrBlack + + /// 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]() + + override public init() { + super.init() + + xOffset = 5.0 + 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 { 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 = 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 + 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 = [NSAttributedString.Key.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) + /// `true` if a custom legend entries has been set + @objc open var isLegendCustom: Bool { + _isLegendCustom + } +} diff --git a/Pods/Charts/Source/Charts/Components/LegendEntry.swift b/Pods/Charts/Source/Charts/Components/LegendEntry.swift new file mode 100644 index 0000000..87906dd --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/LegendEntry.swift @@ -0,0 +1,86 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(ChartLegendEntry) +open class LegendEntry: NSObject { + override public init() { + super.init() + } + + /// - Parameters: + /// - label: The legend entry text. + /// A `nil` label will start a group. + /// - form: The form to draw for this entry. + /// - formSize: Set to NaN to use the legend's default. + /// - formLineWidth: Set to NaN to use the legend's default. + /// - formLineDashPhase: Line dash configuration. + /// - formLineDashLengths: Line dash configurationas NaN to use the legend's default. + /// - 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.nan + + /// Line width used for shapes that consist of lines. + /// + /// Set to NaN to use the legend's default. + @objc open var formLineWidth = 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? +} diff --git a/Pods/Charts/Source/Charts/Components/MarkerImage.swift b/Pods/Charts/Source/Charts/Components/MarkerImage.swift new file mode 100644 index 0000000..e89f08e --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/MarkerImage.swift @@ -0,0 +1,92 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(ChartMarkerImage) +open class MarkerImage: NSObject, IMarker { + /// The marker image to render + @objc open var image: NSUIImage? + + open var offset = 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() + + override public init() { + super.init() + } + + open func offsetForDrawing(atPoint point: CGPoint) -> CGPoint { + var offset = self.offset + + let chart = 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() + } +} diff --git a/Pods/Charts/Source/Charts/Components/MarkerView.swift b/Pods/Charts/Source/Charts/Components/MarkerView.swift new file mode 100644 index 0000000..552320f --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/MarkerView.swift @@ -0,0 +1,89 @@ +// +// 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 CoreGraphics +import Foundation + +#if canImport(AppKit) + import AppKit +#endif + +@objc(ChartMarkerView) +open class MarkerView: NSUIView, IMarker { + open var offset = 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 = bounds.size.width + let height = 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 = offsetForDrawing(atPoint: point) + + context.saveGState() + context.translateBy(x: point.x + offset.x, + y: point.y + offset.y) + NSUIGraphicsPushContext(context) + 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(&loadedObjects) + + if bundle.loadNibNamed( + NSNib.Name(String(describing: self)), + owner: nil, + topLevelObjects: loadedObjectsPointer + ) { + return loadedObjects[0] as? MarkerView + } + + return nil + #endif + } +} diff --git a/Pods/Charts/Source/Charts/Components/XAxis.swift b/Pods/Charts/Source/Charts/Components/XAxis.swift new file mode 100644 index 0000000..a59bf5d --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/XAxis.swift @@ -0,0 +1,71 @@ +// +// 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 CoreGraphics +import Foundation + +@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 + + /// `true` if word wrapping the labels is enabled + @objc open var isWordWrapEnabled: Bool { 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 + + override public init() { + super.init() + + yOffset = 4.0 + } + + @objc open var isAvoidFirstLastClippingEnabled: Bool { + avoidFirstLastClippingEnabled + } +} diff --git a/Pods/Charts/Source/Charts/Components/YAxis.swift b/Pods/Charts/Source/Charts/Components/YAxis.swift new file mode 100644 index 0000000..16051e6 --- /dev/null +++ b/Pods/Charts/Source/Charts/Components/YAxis.swift @@ -0,0 +1,186 @@ +// +// 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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#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 alignment of the text in the y-label + @objc open var labelAlignment: NSTextAlignment = .left + + /// the horizontal offset of the y-label + @objc open var labelXOffset: CGFloat = 0.0 + + /// 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) + + override public init() { + super.init() + + yOffset = 0.0 + } + + @objc public init(position: AxisDependency) { + super.init() + + _axisDependency = position + + yOffset = 0.0 + } + + @objc open var axisDependency: AxisDependency { + _axisDependency + } + + @objc open func requiredSize() -> CGSize { + let label = getLongestLabel() as NSString + var size = label.size(withAttributes: [NSAttributedString.Key.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 { + requiredSize().height + } + + /// `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 { inverted } + + override open 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 + + // Make sure max is greater than min + // Discussion: https://github.com/danielgindi/Charts/pull/3650#discussion_r221409991 + if min > max { + switch (_customAxisMax, _customAxisMin) { + case(true, true): + (min, max) = (max, min) + case(true, false): + min = max < 0 ? max * 1.5 : max * 0.5 + case(false, true): + max = min < 0 ? min * 0.5 : min * 1.5 + case(false, false): + break + } + } + + // 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 { drawBottomYLabelEntryEnabled } + + @objc open var isDrawTopYLabelEntryEnabled: Bool { drawTopYLabelEntryEnabled } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/ChartBaseDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/ChartBaseDataSet.swift new file mode 100644 index 0000000..2c1b6e1 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/ChartBaseDataSet.swift @@ -0,0 +1,381 @@ +// +// 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 CoreGraphics +import Foundation + +open class ChartBaseDataSet: NSObject, IChartDataSet, NSCopying { + override public required 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(.labelOrBlack) + } + + @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(.labelOrBlack) + + 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(_: Int) -> ChartDataEntry? { + fatalError("entryForIndex is not implemented in ChartBaseDataSet") + } + + open func entryForXValue( + _: Double, + closestToY _: Double, + rounding _: ChartDataSetRounding + ) -> ChartDataEntry? { + fatalError("entryForXValue(x, closestToY, rounding) is not implemented in ChartBaseDataSet") + } + + open func entryForXValue( + _: Double, + closestToY _: Double + ) -> ChartDataEntry? { + fatalError("entryForXValue(x, closestToY) is not implemented in ChartBaseDataSet") + } + + open func entriesForXValue(_: Double) -> [ChartDataEntry] { + fatalError("entriesForXValue is not implemented in ChartBaseDataSet") + } + + open func entryIndex( + x _: Double, + closestToY _: Double, + rounding _: ChartDataSetRounding + ) -> Int { + fatalError("entryIndex(x, closestToY, rounding) is not implemented in ChartBaseDataSet") + } + + open func entryIndex(entry _: ChartDataEntry) -> Int { + fatalError("entryIndex(entry) is not implemented in ChartBaseDataSet") + } + + open func addEntry(_: ChartDataEntry) -> Bool { + fatalError("addEntry is not implemented in ChartBaseDataSet") + } + + open func addEntryOrdered(_: ChartDataEntry) -> Bool { + fatalError("addEntryOrdered is not implemented in ChartBaseDataSet") + } + + @discardableResult open func removeEntry(_: 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(_: 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. + /// + /// - Parameters: + /// - 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. + /// + /// - Parameters: + /// - 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. + /// + /// - Parameters: + /// - color: the color to set + /// - 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. + /// + /// - Parameters: + /// - colors: the colors to set + /// - alpha: alpha to apply to the set `colors` + @objc open func setColors(_ colors: [NSUIColor], alpha: CGFloat) { + self.colors = colors.map { $0.withAlphaComponent(alpha) } + } + + /// Sets colors with a specific alpha value. + /// + /// - Parameters: + /// - colors: the colors to set + /// - 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 + + /// `true` if value highlighting is enabled for this dataset + open var isHighlightEnabled: Bool { 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 { + _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 { + 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.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.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.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]? + + /// 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 + + /// `true` if y-value drawing is enabled, `false` ifnot + open var isDrawValuesEnabled: Bool { + 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 { + 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 + + /// `true` if this DataSet is visible inside the chart, or `false` ifit is currently hidden. + open var isVisible: Bool { + visible + } + + // MARK: - NSObject + + override open var description: String { + String(format: "%@, label: %@, %i entries", arguments: [NSStringFromClass(type(of: self)), self.label ?? "", self.entryCount]) + } + + override open var debugDescription: String { + (0 ..< entryCount).reduce(description + ":") { + "\($0)\n\(self.entryForIndex($1)?.description ?? "")" + } + } + + // MARK: - NSCopying + + open func copy(with _: NSZone? = nil) -> Any { + let copy = type(of: self).init() + + copy.colors = colors + copy.valueColors = valueColors + copy.label = label + copy.axisDependency = axisDependency + copy.highlightEnabled = highlightEnabled + copy._valueFormatter = _valueFormatter + copy.valueFont = valueFont + copy.form = form + copy.formSize = formSize + copy.formLineWidth = formLineWidth + copy.formLineDashPhase = formLineDashPhase + copy.formLineDashLengths = formLineDashLengths + copy.drawValuesEnabled = drawValuesEnabled + copy.drawValuesEnabled = drawValuesEnabled + copy.iconsOffset = iconsOffset + copy.visible = visible + + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartData.swift new file mode 100644 index 0000000..0b6d27a --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartData.swift @@ -0,0 +1,95 @@ +// +// 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 CoreGraphics +import Foundation + +open class BarChartData: BarLineScatterCandleBubbleChartData { + override public init() { + super.init() + } + + override public 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. + /// + /// - Parameters: + /// - fromX: the starting point on the x-axis where the grouping should begin + /// - groupSpace: The space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f + /// - 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 = 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. + /// + /// - Parameters: + /// - groupSpace: + /// - barSpace: + @objc open func groupWidth(groupSpace: Double, barSpace: Double) -> Double { + Double(_dataSets.count) * (barWidth + barSpace) + groupSpace + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift new file mode 100644 index 0000000..f998e15 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift @@ -0,0 +1,197 @@ +// +// 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). + override public init(x: Double, y: Double) { + super.init(x: x, y: y) + } + + /// Constructor for normal bars (not stacked). + public convenience init(x: Double, y: Double, data: Any?) { + self.init(x: x, y: y) + self.data = data + } + + /// Constructor for normal bars (not stacked). + public convenience init(x: Double, y: Double, icon: NSUIImage?) { + self.init(x: x, y: y) + self.icon = icon + } + + /// Constructor for normal bars (not stacked). + public convenience init(x: Double, y: Double, icon: NSUIImage?, data: Any?) { + self.init(x: x, y: y) + self.icon = icon + self.data = data + } + + /// Constructor for stacked bar entries. + @objc public init(x: Double, yValues: [Double]) { + super.init(x: x, y: BarChartDataEntry.calcSum(values: yValues)) + _yVals = yValues + calcPosNegSum() + calcRanges() + } + + /// Constructor for stacked bar entries. One data object for whole stack + @objc public convenience init(x: Double, yValues: [Double], icon: NSUIImage?) { + self.init(x: x, yValues: yValues) + self.icon = icon + } + + /// Constructor for stacked bar entries. One data object for whole stack + @objc public convenience init(x: Double, yValues: [Double], data: Any?) { + self.init(x: x, yValues: yValues) + self.data = data + } + + /// Constructor for stacked bar entries. One data object for whole stack + @objc public convenience init(x: Double, yValues: [Double], icon: NSUIImage?, data: Any?) { + self.init(x: x, yValues: yValues) + self.icon = icon + self.data = data + } + + @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 + } + + /// The sum of all negative values this entry (if stacked) contains. (this is a positive number) + @objc open var negativeSum: Double { + _negativeSum + } + + /// The sum of all positive values this entry (if stacked) contains. + @objc open var positiveSum: Double { + _positiveSum + } + + @objc open func calcPosNegSum() { + (_negativeSum, _positiveSum) = _yVals?.reduce(into: (0, 0)) { result, y in + if y < 0 { + result.0 += -y + } else { + result.1 += y + } + } ?? (0, 0) + } + + /// Splits up the stack-values of the given bar-entry into Range objects. + /// + /// - Parameters: + /// - entry: + /// - Returns: + @objc open func calcRanges() { + guard let values = yValues, !values.isEmpty else { return } + + if _ranges == nil { + _ranges = [Range]() + } else { + _ranges!.removeAll() + } + + _ranges!.reserveCapacity(values.count) + + var negRemain = -negativeSum + var posRemain: Double = 0.0 + + for value in values { + 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 { _yVals != nil } + + /// the values the stacked barchart holds + @objc open var yValues: [Double]? { + get { _yVals } + set { + y = BarChartDataEntry.calcSum(values: newValue) + _yVals = newValue + calcPosNegSum() + calcRanges() + } + } + + /// The ranges of the individual stack-entries. Will return null if this entry is not stacked. + @objc open var ranges: [Range]? { + _ranges + } + + // MARK: NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! BarChartDataEntry + copy._yVals = _yVals + copy.y = y + copy._negativeSum = _negativeSum + copy._positiveSum = _positiveSum + return copy + } + + /// Calculates the sum across all values of the given stack. + /// + /// - Parameters: + /// - 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 + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift new file mode 100644 index 0000000..8dbbd24 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift @@ -0,0 +1,140 @@ +// +// 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 CoreGraphics +import Foundation + +open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBarChartDataSet { + private func initialize() { + highlightColor = NSUIColor.black + + calcStackSize(entries: entries as! [BarChartDataEntry]) + calcEntryCountIncludingStacks(entries: entries as! [BarChartDataEntry]) + } + + public required init() { + super.init() + initialize() + } + + override public init(entries: [ChartDataEntry]?, label: String?) { + super.init(entries: entries, 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 + } + } + } + } + + override open 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) + } + } + + /// The maximum number of bars that can be stacked upon another in this DataSet. + open var stackSize: Int { + _stackSize + } + + /// `true` if this DataSet is stacked (stacksize > 1) or not. + open var isStacked: Bool { + _stackSize > 1 ? true : false + } + + /// The overall entry count, including counting each stack-value individually + @objc open var entryCountStacks: Int { + _entryCountStacks + } + + /// array of labels used to describe the different values of the stacked bars + open var stackLabels: [String] = [] + + // 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 + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! BarChartDataSet + copy._stackSize = _stackSize + copy._entryCountStacks = _entryCountStacks + copy.stackLabels = stackLabels + + copy.barShadowColor = barShadowColor + copy.barBorderWidth = barBorderWidth + copy.barBorderColor = barBorderColor + copy.highlightAlpha = highlightAlpha + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift new file mode 100644 index 0000000..f5aeb3b --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift @@ -0,0 +1,22 @@ +// +// 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 { + override public init() { + super.init() + } + + override public init(dataSets: [IChartDataSet]?) { + super.init(dataSets: dataSets) + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift new file mode 100644 index 0000000..50b6c5d --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift @@ -0,0 +1,35 @@ +// +// 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 CoreGraphics +import Foundation + +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 + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! BarLineScatterCandleBubbleChartDataSet + copy.highlightColor = highlightColor + copy.highlightLineWidth = highlightLineWidth + copy.highlightLineDashPhase = highlightLineDashPhase + copy.highlightLineDashLengths = highlightLineDashLengths + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartData.swift new file mode 100644 index 0000000..2476a04 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartData.swift @@ -0,0 +1,28 @@ +// +// BubbleChartData.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import CoreGraphics +import Foundation + +open class BubbleChartData: BarLineScatterCandleBubbleChartData { + override public init() { + super.init() + } + + override public 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 } + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift new file mode 100644 index 0000000..8d3e7d8 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift @@ -0,0 +1,72 @@ +// +// BubbleDataEntry.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import CoreGraphics +import Foundation + +open class BubbleChartDataEntry: ChartDataEntry { + /// The size of the bubble. + @objc open var size = CGFloat(0.0) + + public required init() { + super.init() + } + + /// - Parameters: + /// - x: The index on the x-axis. + /// - y: The value on the y-axis. + /// - size: The size of the bubble. + @objc public init(x: Double, y: Double, size: CGFloat) { + super.init(x: x, y: y) + + self.size = size + } + + /// - Parameters: + /// - x: The index on the x-axis. + /// - y: The value on the y-axis. + /// - size: The size of the bubble. + /// - data: Spot for additional data this Entry represents. + @objc public convenience init(x: Double, y: Double, size: CGFloat, data: Any?) { + self.init(x: x, y: y, size: size) + self.data = data + } + + /// - Parameters: + /// - x: The index on the x-axis. + /// - y: The value on the y-axis. + /// - size: The size of the bubble. + /// - icon: icon image + @objc public convenience init(x: Double, y: Double, size: CGFloat, icon: NSUIImage?) { + self.init(x: x, y: y, size: size) + self.icon = icon + } + + /// - Parameters: + /// - x: The index on the x-axis. + /// - y: The value on the y-axis. + /// - size: The size of the bubble. + /// - icon: icon image + /// - data: Spot for additional data this Entry represents. + @objc public convenience init(x: Double, y: Double, size: CGFloat, icon: NSUIImage?, data: Any?) { + self.init(x: x, y: y, size: size) + self.icon = icon + self.data = data + } + + // MARK: NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! BubbleChartDataEntry + copy.size = size + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift new file mode 100644 index 0000000..0c90631 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift @@ -0,0 +1,53 @@ +// +// BubbleChartDataSet.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import CoreGraphics +import Foundation + +open class BubbleChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBubbleChartDataSet { + // MARK: - Data functions and accessors + + internal var _maxSize = CGFloat(0.0) + + open var maxSize: CGFloat { _maxSize } + @objc open var normalizeSizeEnabled: Bool = true + open var isNormalizeSizeEnabled: Bool { normalizeSizeEnabled } + + override open 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 + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! BubbleChartDataSet + copy._xMin = _xMin + copy._xMax = _xMax + copy._maxSize = _maxSize + copy.normalizeSizeEnabled = normalizeSizeEnabled + copy.highlightCircleWidth = highlightCircleWidth + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartData.swift new file mode 100644 index 0000000..8524e81 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartData.swift @@ -0,0 +1,22 @@ +// +// 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 { + override public init() { + super.init() + } + + override public init(dataSets: [IChartDataSet]?) { + super.init(dataSets: dataSets) + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift new file mode 100644 index 0000000..7cb82d2 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift @@ -0,0 +1,86 @@ +// +// 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) + + high = shadowH + low = shadowL + self.open = open + self.close = close + } + + @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, icon: NSUIImage?) { + self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) + self.icon = icon + } + + @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, data: Any?) { + self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) + self.data = data + } + + @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, icon: NSUIImage?, data: Any?) { + self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) + self.icon = icon + self.data = data + } + + /// The overall range (difference) between shadow-high and shadow-low. + @objc open var shadowRange: Double { + abs(high - low) + } + + /// The body size (difference between open and close). + @objc open var bodyRange: Double { + abs(open - close) + } + + /// the center value of the candle. (Middle value between high and low) + override open var y: Double { + get { + super.y + } + set { + super.y = (high + low) / 2.0 + } + } + + // MARK: NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! CandleChartDataEntry + copy.high = high + copy.low = low + copy.open = open + copy.close = close + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift new file mode 100644 index 0000000..d32402a --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift @@ -0,0 +1,120 @@ +// +// 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 CoreGraphics +import Foundation + +open class CandleChartDataSet: LineScatterCandleRadarChartDataSet, ICandleChartDataSet { + public required init() { + super.init() + } + + override public init(entries: [ChartDataEntry]?, label: String?) { + super.init(entries: entries, label: label) + } + + // MARK: - Data functions and accessors + + override open 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) + } + + override open 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 { + get { + _barSpace + } + set { + _barSpace = newValue.clamped(to: 0 ... 0.45) + } + } + + /// 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 { 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 { 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 { decreasingFilled } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartData.swift new file mode 100644 index 0000000..b34dc6f --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -0,0 +1,497 @@ +// +// 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.greatestFiniteMagnitude + internal var _xMax: Double = -Double.greatestFiniteMagnitude + internal var _xMin = Double.greatestFiniteMagnitude + internal var _leftAxisMax: Double = -Double.greatestFiniteMagnitude + internal var _leftAxisMin = Double.greatestFiniteMagnitude + internal var _rightAxisMax: Double = -Double.greatestFiniteMagnitude + internal var _rightAxisMin = Double.greatestFiniteMagnitude + + internal var _dataSets = [IChartDataSet]() + + override public init() { + super.init() + + _dataSets = [IChartDataSet]() + } + + @objc public init(dataSets: [IChartDataSet]?) { + super.init() + + _dataSets = dataSets ?? [IChartDataSet]() + + 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) { + _dataSets.forEach { $0.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 + + _dataSets.forEach { calcMinMax(dataSet: $0) } + + _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 + } + } + } + + /// The number of LineDataSets this object contains + @objc open var dataSetCount: Int { + _dataSets.count + } + + /// The smallest y-value the data object contains. + @objc open var yMin: Double { + _yMin + } + + @nonobjc + open func getYMin() -> Double { + _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 + } + } + } + + /// The greatest y-value the data object contains. + @objc open var yMax: Double { + _yMax + } + + @nonobjc + open func getYMax() -> Double { + _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 + } + } + } + + /// The minimum x-value the data object contains. + @objc open var xMin: Double { + _xMin + } + + /// The maximum x-value the data object contains. + @objc open var xMax: Double { + _xMax + } + + /// All DataSet objects this ChartData object holds. + @objc open var dataSets: [IChartDataSet] { + get { + _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.** + /// + /// - Parameters: + /// - dataSets: the DataSet array to search + /// - type: + /// - 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 { + // TODO: Return nil instead of -1 + if ignorecase { + return dataSets.firstIndex { $0.label?.caseInsensitiveCompare(label) == .orderedSame } + ?? -1 + } else { + return dataSets.firstIndex { $0.label == label } + ?? -1 + } + } + + /// Get the Entry for a corresponding highlight object + /// + /// - Parameters: + /// - 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.** + /// + /// - Parameters: + /// - label: + /// - 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 { + guard let i = _dataSets.firstIndex(where: { $0 === dataSet }) else { return false } + return removeDataSetByIndex(i) + } + + /// 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? { + _dataSets.first { $0.entryForXValue(e.x, closestToY: e.y) === e } + } + + /// - 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 { + // TODO: Return nil instead of -1 + _dataSets.firstIndex { $0 === dataSet } ?? -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? { + dataSets.first { $0.axisDependency == .left } + } + + /// - 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? { + dataSets.first { $0.axisDependency == .right } + } + + /// - Returns: All colors used across all DataSet objects this object represents. + @objc open func getColors() -> [NSUIColor]? { + // TODO: Don't return nil + _dataSets.flatMap(\.colors) + } + + /// Sets a custom IValueFormatter for all DataSets this data object contains. + @objc open func setValueFormatter(_ formatter: IValueFormatter) { + dataSets.forEach { $0.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) { + dataSets.forEach { $0.valueTextColor = color } + } + + /// Sets the font for all value-labels for all DataSets this data object contains. + @objc open func setValueFont(_ font: NSUIFont) { + dataSets.forEach { $0.valueFont = font } + } + + /// Enables / disables drawing values (value-text) for all DataSets this data object contains. + @objc open func setDrawValues(_ enabled: Bool) { + dataSets.forEach { $0.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 { dataSets.allSatisfy { $0.highlightEnabled } } + set { dataSets.forEach { $0.highlightEnabled = newValue } } + } + + /// if true, value highlightning is enabled + @objc open var isHighlightEnabled: Bool { 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 { + dataSets.contains { $0 === dataSet } + } + + /// The total entry count across all DataSet objects this data object contains. + @objc open var entryCount: Int { + _dataSets.reduce(0) { $0 + $1.entryCount } + } + + /// The DataSet object with the maximum number of entries or null if there are no DataSets. + @objc open var maxEntryCountSet: IChartDataSet? { + dataSets.max { $0.entryCount < $1.entryCount } + } + + // MARK: - Accessibility + + /// When the data entry labels are generated identifiers, set this property to prepend a string before each identifier + /// + /// For example, if a label is "#3", settings this property to "Item" allows it to be spoken as "Item #3" + @objc open var accessibilityEntryLabelPrefix: String? + + /// When the data entry value requires a unit, use this property to append the string representation of the unit to the value + /// + /// For example, if a value is "44.1", setting this property to "m" allows it to be spoken as "44.1 m" + @objc open var accessibilityEntryLabelSuffix: String? + + /// If the data entry value is a count, set this to true to allow plurals and other grammatical changes + /// **default**: false + @objc open var accessibilityEntryLabelSuffixIsCount: Bool = false +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift new file mode 100644 index 0000000..e9cb4cd --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift @@ -0,0 +1,102 @@ +// +// 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, NSCopying { + /// the x value + @objc open var x = 0.0 + + public required init() { + super.init() + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - x: the x value + /// - 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. + /// + /// - Parameters: + /// - x: the x value + /// - y: the y value (the actual value of the entry) + /// - data: Space for additional data this Entry represents. + + @objc public convenience init(x: Double, y: Double, data: Any?) { + self.init(x: x, y: y) + self.data = data + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - x: the x value + /// - y: the y value (the actual value of the entry) + /// - icon: icon image + + @objc public convenience init(x: Double, y: Double, icon: NSUIImage?) { + self.init(x: x, y: y) + self.icon = icon + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - x: the x value + /// - y: the y value (the actual value of the entry) + /// - icon: icon image + /// - data: Space for additional data this Entry represents. + + @objc public convenience init(x: Double, y: Double, icon: NSUIImage?, data: Any?) { + self.init(x: x, y: y) + self.icon = icon + self.data = data + } + + // MARK: NSObject + + override open var description: String { + "ChartDataEntry, x: \(x), y \(y)" + } + + // MARK: NSCopying + + open func copy(with _: NSZone? = nil) -> Any { + let copy = type(of: self).init() + + copy.x = x + copy.y = y + copy.data = data + + return copy + } +} + +// MARK: Equatable + +extension ChartDataEntry /*: Equatable */ { + override open func isEqual(_ object: Any?) -> Bool { + guard let object = object as? ChartDataEntry else { return false } + + if self === object { + return true + } + + return y == object.y + && x == object.x + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift new file mode 100644 index 0000000..ead75c1 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift @@ -0,0 +1,89 @@ +// +// 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 = 0.0 + + /// optional spot for additional data this Entry represents + @objc open var data: Any? + + /// optional icon image + @objc open var icon: NSUIImage? + + override public required init() { + super.init() + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - y: the y value (the actual value of the entry) + @objc public init(y: Double) { + super.init() + + self.y = y + } + + /// - Parameters: + /// - y: the y value (the actual value of the entry) + /// - data: Space for additional data this Entry represents. + + @objc public convenience init(y: Double, data: Any?) { + self.init(y: y) + + self.data = data + } + + /// - Parameters: + /// - y: the y value (the actual value of the entry) + /// - icon: icon image + + @objc public convenience init(y: Double, icon: NSUIImage?) { + self.init(y: y) + + self.icon = icon + } + + /// - Parameters: + /// - y: the y value (the actual value of the entry) + /// - icon: icon image + /// - data: Space for additional data this Entry represents. + + @objc public convenience init(y: Double, icon: NSUIImage?, data: Any?) { + self.init(y: y) + + self.icon = icon + self.data = data + } + + // MARK: NSObject + + override open var description: String { + "ChartDataEntryBase, y \(y)" + } +} + +// MARK: Equatable + +extension ChartDataEntryBase /*: Equatable */ { + override open func isEqual(_ object: Any?) -> Bool { + guard let object = object as? ChartDataEntryBase else { return false } + + if self === object { + return true + } + + return y == object.y + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift new file mode 100644 index 0000000..734aa30 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -0,0 +1,515 @@ +// +// 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() { + entries = [] + + super.init() + } + + override public convenience init(label: String?) { + self.init(entries: nil, label: label) + } + + @objc public init(entries: [ChartDataEntry]?, label: String?) { + self.entries = entries ?? [] + + super.init(label: label) + + calcMinMax() + } + + @objc public convenience init(entries: [ChartDataEntry]?) { + self.init(entries: entries, 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 + @available(*, unavailable, renamed: "entries") + @objc + open var values: [ChartDataEntry] { entries } + + @objc + open private(set) var entries: [ChartDataEntry] + + /// Used to replace all entries of a data set while retaining styling properties. + /// This is a separate method from a setter on `entries` to encourage usage + /// of `Collection` conformances. + /// + /// - Parameter entries: new entries to replace existing entries in the dataset + @objc + public func replaceEntries(_ entries: [ChartDataEntry]) { + self.entries = entries + notifyDataSetChanged() + } + + /// maximum y-value in the value array + internal var _yMax: Double = -Double.greatestFiniteMagnitude + + /// minimum y-value in the value array + internal var _yMin = 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.greatestFiniteMagnitude + + override open func calcMinMax() { + _yMax = -Double.greatestFiniteMagnitude + _yMin = Double.greatestFiniteMagnitude + _xMax = -Double.greatestFiniteMagnitude + _xMin = Double.greatestFiniteMagnitude + + guard !isEmpty else { return } + + forEach(calcMinMax) + } + + override open func calcMinMaxY(fromX: Double, toX: Double) { + _yMax = -Double.greatestFiniteMagnitude + _yMin = Double.greatestFiniteMagnitude + + guard !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 } + // only recalculate y + self[indexFrom ... indexTo].forEach(calcMinMaxY) + } + + @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. + /// + /// - Parameters: + /// - e: + internal func calcMinMax(entry e: ChartDataEntry) { + calcMinMaxX(entry: e) + calcMinMaxY(entry: e) + } + + /// The minimum y-value this DataSet holds + override open var yMin: Double { _yMin } + + /// The maximum y-value this DataSet holds + override open var yMax: Double { _yMax } + + /// The minimum x-value this DataSet holds + override open var xMin: Double { _xMin } + + /// The maximum x-value this DataSet holds + override open var xMax: Double { _xMax } + + /// The number of y-values this DataSet represents + @available(*, deprecated, message: "Use `count` instead") + override open var entryCount: Int { count } + + /// - Throws: out of bounds + /// if `i` is out of bounds, it may throw an out-of-bounds exception + /// - Returns: The entry object found at the given index (not x-value!) + @available(*, deprecated, message: "Use `subscript(index:)` instead.") + override open func entryForIndex(_ i: Int) -> ChartDataEntry? { + guard i >= startIndex, i < endIndex else { + return nil + } + return self[i] + } + + /// - Parameters: + /// - xValue: the x-value + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - rounding: determine whether to round up/down/closest if there is no Entry matching the provided x-value + /// - 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. + override open func entryForXValue( + _ xValue: Double, + closestToY yValue: Double, + rounding: ChartDataSetRounding + ) -> ChartDataEntry? { + let index = entryIndex(x: xValue, closestToY: yValue, rounding: rounding) + if index > -1 { + return self[index] + } + return nil + } + + /// - Parameters: + /// - xValue: the x-value + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - 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. + override open func entryForXValue( + _ xValue: Double, + closestToY yValue: Double + ) -> ChartDataEntry? { + 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. + override open func entriesForXValue(_ xValue: Double) -> [ChartDataEntry] { + var entries = [ChartDataEntry]() + + var low = startIndex + var high = endIndex - 1 + + while low <= high { + var m = (high + low) / 2 + var entry = self[m] + + // if we have a match + if xValue == entry.x { + while m > 0, self[m - 1].x == xValue { + m -= 1 + } + + high = endIndex + + // loop over all "equal" entries + while m < high { + entry = self[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 + } + + /// - Parameters: + /// - xValue: x-value of the entry to search for + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - rounding: Rounding method if exact value was not found + /// - 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. + override open func entryIndex( + x xValue: Double, + closestToY yValue: Double, + rounding: ChartDataSetRounding + ) -> Int { + var low = startIndex + var high = endIndex - 1 + var closest = high + + while low < high { + let m = (low + high) / 2 + + let d1 = self[m].x - xValue + let d2 = self[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 = self[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 < 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, self[closest - 1].x == closestXValue { + closest -= 1 + } + + var closestYValue = self[closest].y + var closestYIndex = closest + + while true { + closest += 1 + if closest >= endIndex { break } + + let value = self[closest] + + if value.x != closestXValue { break } + if abs(value.y - yValue) <= abs(closestYValue - yValue) { + closestYValue = yValue + closestYIndex = closest + } + } + + closest = closestYIndex + } + } + + return closest + } + + /// - Parameters: + /// - e: the entry to search for + /// - Returns: The array-index of the specified entry + @available(*, deprecated, message: "Use `firstIndex(of:)` or `lastIndex(of:)`") + override open func entryIndex(entry e: ChartDataEntry) -> Int { + firstIndex(of: e) ?? -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. + /// + /// - Parameters: + /// - e: the entry to add + /// - Returns: True + @available(*, deprecated, message: "Use `append(_:)` instead") + override open func addEntry(_ e: ChartDataEntry) -> Bool { + 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. + /// + /// - Parameters: + /// - e: the entry to add + /// - Returns: True + override open func addEntryOrdered(_ e: ChartDataEntry) -> Bool { + calcMinMax(entry: e) + + if let last = last, last.x > e.x { + var closestIndex = entryIndex(x: e.x, closestToY: e.y, rounding: .up) + while self[closestIndex].x < e.x { + closestIndex += 1 + } + entries.insert(e, at: closestIndex) + } else { + append(e) + } + + return true + } + + @available(*, renamed: "remove(_:)") + override open func removeEntry(_ entry: ChartDataEntry) -> Bool { + remove(entry) + } + + /// Removes an Entry from the DataSet dynamically. + /// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum. + /// + /// - Parameters: + /// - entry: the entry to remove + /// - Returns: `true` if the entry was removed successfully, else if the entry does not exist + open func remove(_ entry: ChartDataEntry) -> Bool { + guard let index = firstIndex(of: entry) else { return false } + _ = remove(at: index) + return true + } + + /// Removes the first Entry (at index 0) of this DataSet from the entries array. + /// + /// - Returns: `true` if successful, `false` if not. + @available(*, deprecated, message: "Use `func removeFirst() -> ChartDataEntry` instead.") + override open func removeFirst() -> Bool { + let entry: ChartDataEntry? = isEmpty ? nil : 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. + @available(*, deprecated, message: "Use `func removeLast() -> ChartDataEntry` instead.") + override open func removeLast() -> Bool { + let entry: ChartDataEntry? = isEmpty ? nil : removeLast() + return entry != nil + } + + /// Removes all values from this DataSet and recalculates min and max value. + @available(*, deprecated, message: "Use `removeAll(keepingCapacity:)` instead.") + override open func clear() { + removeAll(keepingCapacity: true) + } + + // MARK: - Data functions and accessors + + // MARK: - NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! ChartDataSet + + copy.entries = entries + copy._yMax = _yMax + copy._yMin = _yMin + copy._xMax = _xMax + copy._xMin = _xMin + + return copy + } +} + +// MARK: MutableCollection + +extension ChartDataSet: MutableCollection { + public typealias Index = Int + public typealias Element = ChartDataEntry + + public var startIndex: Index { + entries.startIndex + } + + public var endIndex: Index { + entries.endIndex + } + + public func index(after: Index) -> Index { + entries.index(after: after) + } + + @objc + public subscript(position: Index) -> Element { + get { + // This is intentionally not a safe subscript to mirror + // the behaviour of the built in Swift Collection Types + entries[position] + } + set { + calcMinMax(entry: newValue) + entries[position] = newValue + } + } +} + +// MARK: RandomAccessCollection + +extension ChartDataSet: RandomAccessCollection { + public func index(before: Index) -> Index { + entries.index(before: before) + } +} + +// MARK: RangeReplaceableCollection + +extension ChartDataSet: RangeReplaceableCollection { + public func append(_ newElement: Element) { + calcMinMax(entry: newElement) + entries.append(newElement) + } + + public func remove(at position: Index) -> Element { + let element = entries.remove(at: position) + notifyDataSetChanged() + return element + } + + public func removeFirst() -> Element { + let element = entries.removeFirst() + notifyDataSetChanged() + return element + } + + public func removeFirst(_ n: Int) { + entries.removeFirst(n) + notifyDataSetChanged() + } + + public func removeLast() -> Element { + let element = entries.removeLast() + notifyDataSetChanged() + return element + } + + public func removeLast(_ n: Int) { + entries.removeLast(n) + notifyDataSetChanged() + } + + public func removeSubrange(_ bounds: R) where R: RangeExpression, Index == R.Bound { + entries.removeSubrange(bounds) + notifyDataSetChanged() + } + + @objc + public func removeAll(keepingCapacity keepCapacity: Bool) { + entries.removeAll(keepingCapacity: keepCapacity) + notifyDataSetChanged() + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/CombinedChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/CombinedChartData.swift new file mode 100644 index 0000000..575070f --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/CombinedChartData.swift @@ -0,0 +1,245 @@ +// +// 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! + + override public init() { + super.init() + } + + override public init(dataSets: [IChartDataSet]?) { + super.init(dataSets: dataSets) + } + + @objc open var lineData: LineChartData! { + get { + _lineData + } + set { + _lineData = newValue + notifyDataChanged() + } + } + + @objc open var barData: BarChartData! { + get { + _barData + } + set { + _barData = newValue + notifyDataChanged() + } + } + + @objc open var scatterData: ScatterChartData! { + get { + _scatterData + } + set { + _scatterData = newValue + notifyDataChanged() + } + } + + @objc open var candleData: CandleChartData! { + get { + _candleData + } + set { + _candleData = newValue + notifyDataChanged() + } + } + + @objc open var bubbleData: BubbleChartData! { + get { + _bubbleData + } + set { + _bubbleData = newValue + notifyDataChanged() + } + } + + override open 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 + } + } + } + } + } + + /// 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 { + allData[index] + } + + open func dataIndex(_ data: ChartData) -> Int? { + allData.firstIndex(of: data) + } + + override open func removeDataSet(_ dataSet: IChartDataSet) -> Bool { + allData.contains { $0.removeDataSet(dataSet) } + } + + override open func removeDataSetByIndex(_: Int) -> Bool { + print("removeDataSet(index) not supported for CombinedData", terminator: "\n") + return false + } + + override open func removeEntry(_: ChartDataEntry, dataSetIndex _: Int) -> Bool { + print("removeEntry(entry, dataSetIndex) not supported for CombinedData", terminator: "\n") + return false + } + + override open func removeEntry(xValue _: Double, dataSetIndex _: Int) -> Bool { + print("removeEntry(xValue, dataSetIndex) not supported for CombinedData", terminator: "\n") + return false + } + + override open 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 + /// + /// - Parameters: + /// - highlight: + /// - Returns: The entry that is highlighted + override open 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) + return entries.first { $0.y == highlight.y || highlight.y.isNaN } + } + + /// Get dataset for highlight + /// + /// - Parameters: + /// - 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] + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartData.swift new file mode 100644 index 0000000..2d7c11c --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartData.swift @@ -0,0 +1,23 @@ +// +// 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 { + override public init() { + super.init() + } + + override public init(dataSets: [IChartDataSet]?) { + super.init(dataSets: dataSets) + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift new file mode 100644 index 0000000..a39330c --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift @@ -0,0 +1,155 @@ +// +// 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 CoreGraphics +import Foundation + +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() + } + + override public init(entries: [ChartDataEntry]?, label: String?) { + super.init(entries: entries, 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.linear + + private var _cubicIntensity = CGFloat(0.2) + + /// Intensity for cubic lines (min = 0.05, max = 1) + /// + /// **default**: 0.2 + open var cubicIntensity: CGFloat { + get { + _cubicIntensity + } + set { + _cubicIntensity = newValue.clamped(to: 0.05 ... 1) + } + } + + /// 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(_: Int) { + circleColors.removeAll(keepingCapacity: false) + } + + /// If true, drawing circles is enabled + open var drawCirclesEnabled = true + + /// `true` if drawing circles for this DataSet is enabled, `false` ifnot + open var isDrawCirclesEnabled: Bool { 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 + + /// `true` if drawing the circle-holes is enabled, `false` ifnot. + open var isDrawCircleHoleEnabled: Bool { 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 { + _fillFormatter + } + set { + _fillFormatter = newValue ?? DefaultFillFormatter() + } + } + + // MARK: NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! LineChartDataSet + copy.circleColors = circleColors + copy.circleHoleColor = circleHoleColor + copy.circleRadius = circleRadius + copy.circleHoleRadius = circleHoleRadius + copy.cubicIntensity = cubicIntensity + copy.lineDashPhase = lineDashPhase + copy.lineDashLengths = lineDashLengths + copy.lineCapType = lineCapType + copy.drawCirclesEnabled = drawCirclesEnabled + copy.drawCircleHoleEnabled = drawCircleHoleEnabled + copy.mode = mode + copy._fillFormatter = _fillFormatter + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift new file mode 100644 index 0000000..2c556aa --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift @@ -0,0 +1,75 @@ +// +// 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 CoreGraphics +import Foundation + +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 { _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 { + _lineWidth + } + set { + _lineWidth = newValue.clamped(to: 0 ... 10) + } + } + + /// 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 + + /// `true` if filled drawing is enabled, `false` ifnot + open var isDrawFilledEnabled: Bool { + drawFilledEnabled + } + + // MARK: NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! LineRadarChartDataSet + copy.fill = fill + copy.fillAlpha = fillAlpha + copy._fillColor = _fillColor + copy._lineWidth = _lineWidth + copy.drawFilledEnabled = drawFilledEnabled + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift new file mode 100644 index 0000000..a1b7ac5 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift @@ -0,0 +1,46 @@ +// +// 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 + + /// `true` if horizontal highlight indicator lines are enabled (drawn) + open var isHorizontalHighlightIndicatorEnabled: Bool { drawHorizontalHighlightIndicatorEnabled } + + /// `true` if vertical highlight indicator lines are enabled (drawn) + open var isVerticalHighlightIndicatorEnabled: Bool { drawVerticalHighlightIndicatorEnabled } + + /// Enables / disables both vertical and horizontal highlight-indicators. + /// :param: enabled + open func setDrawHighlightIndicators(_ enabled: Bool) { + drawHorizontalHighlightIndicatorEnabled = enabled + drawVerticalHighlightIndicatorEnabled = enabled + } + + // MARK: NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! LineScatterCandleRadarChartDataSet + copy.drawHorizontalHighlightIndicatorEnabled = drawHorizontalHighlightIndicatorEnabled + copy.drawVerticalHighlightIndicatorEnabled = drawVerticalHighlightIndicatorEnabled + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartData.swift new file mode 100644 index 0000000..9280c1e --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartData.swift @@ -0,0 +1,98 @@ +// +// 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 { + override public init() { + super.init() + } + + override public init(dataSets: [IChartDataSet]?) { + super.init(dataSets: dataSets) + } + + /// All DataSet objects this ChartData object holds. + @objc override open 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 { + dataSets.count > 0 ? dataSets[0] as? IPieChartDataSet : nil + } + set { + if let newValue = newValue { + dataSets = [newValue] + } else { + dataSets = [] + } + } + } + + override open func getDataSetByIndex(_ index: Int) -> IChartDataSet? { + if index != 0 { + return nil + } + return super.getDataSetByIndex(index) + } + + override open 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 + } + + override open func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry? { + dataSet?.entryForIndex(Int(highlight.x)) + } + + override open 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. + override open func removeDataSetByIndex(_ index: Int) -> Bool { + if index >= _dataSets.count || index < 0 { + return false + } + + return false + } + + /// 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 } + return (0 ..< dataSet.entryCount).reduce(into: 0) { + $0 += dataSet.entryForIndex($1)?.y ?? 0 + } + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataEntry.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataEntry.swift new file mode 100644 index 0000000..3483020 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataEntry.swift @@ -0,0 +1,106 @@ +// +// 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 CoreGraphics +import Foundation + +open class PieChartDataEntry: ChartDataEntry { + public required init() { + super.init() + } + + /// - Parameters: + /// - value: The value on the y-axis + @objc public init(value: Double) { + super.init(x: .nan, y: value) + } + + /// - Parameters: + /// - value: The value on the y-axis + /// - label: The label for the x-axis + @objc public convenience init(value: Double, label: String?) { + self.init(value: value) + self.label = label + } + + /// - Parameters: + /// - value: The value on the y-axis + /// - label: The label for the x-axis + /// - data: Spot for additional data this Entry represents + @objc public convenience init(value: Double, label: String?, data: Any?) { + self.init(value: value, label: label, icon: nil, data: data) + } + + /// - Parameters: + /// - value: The value on the y-axis + /// - label: The label for the x-axis + /// - icon: icon image + @objc public convenience init(value: Double, label: String?, icon: NSUIImage?) { + self.init(value: value) + self.label = label + self.icon = icon + } + + /// - Parameters: + /// - value: The value on the y-axis + /// - label: The label for the x-axis + /// - icon: icon image + /// - data: Spot for additional data this Entry represents + @objc public convenience init(value: Double, label: String?, icon: NSUIImage?, data: Any?) { + self.init(value: value) + self.label = label + self.icon = icon + self.data = data + } + + /// - Parameters: + /// - value: The value on the y-axis + /// - data: Spot for additional data this Entry represents + @objc public convenience init(value: Double, data: Any?) { + self.init(value: value) + self.data = data + } + + /// - Parameters: + /// - value: The value on the y-axis + /// - icon: icon image + @objc public convenience init(value: Double, icon: NSUIImage?) { + self.init(value: value) + self.icon = icon + } + + /// - Parameters: + /// - value: The value on the y-axis + /// - icon: icon image + /// - data: Spot for additional data this Entry represents + @objc public convenience init(value: Double, icon: NSUIImage?, data: Any?) { + self.init(value: value) + self.icon = icon + self.data = data + } + + // MARK: Data property accessors + + @objc open var label: String? + + @objc open var value: Double { + get { y } + set { y = newValue } + } + + // MARK: NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! PieChartDataEntry + copy.label = label + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift new file mode 100644 index 0000000..0c25786 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift @@ -0,0 +1,123 @@ +// +// 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 CoreGraphics +import Foundation + +open class PieChartDataSet: ChartDataSet, IPieChartDataSet { + @objc(PieChartValuePosition) + public enum ValuePosition: Int { + case insideSlice + case outsideSlice + } + + private func initialize() { + valueTextColor = NSUIColor.white + valueFont = NSUIFont.systemFont(ofSize: 13.0) + } + + public required init() { + super.init() + initialize() + } + + override public init(entries: [ChartDataEntry]?, label: String?) { + super.init(entries: entries, label: label) + initialize() + } + + override internal 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 { + _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 and enabled, line will have the same color as the slice + open var useValueColorForLine: Bool = false + + /// 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? + + /// the color for the slice-text labels + open var entryLabelColor: NSUIColor? + + /// the color for the highlighted sector + open var highlightColor: NSUIColor? + + // MARK: - NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! PieChartDataSet + copy._sliceSpace = _sliceSpace + copy.automaticallyDisableSliceSpacing = automaticallyDisableSliceSpacing + copy.selectionShift = selectionShift + copy.xValuePosition = xValuePosition + copy.yValuePosition = yValuePosition + copy.valueLineColor = valueLineColor + copy.valueLineWidth = valueLineWidth + copy.valueLinePart1OffsetPercentage = valueLinePart1OffsetPercentage + copy.valueLinePart1Length = valueLinePart1Length + copy.valueLinePart2Length = valueLinePart2Length + copy.valueLineVariableLength = valueLineVariableLength + copy.entryLabelFont = entryLabelFont + copy.entryLabelColor = entryLabelColor + copy.highlightColor = highlightColor + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartData.swift new file mode 100644 index 0000000..69bdacb --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartData.swift @@ -0,0 +1,40 @@ +// +// 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 CoreGraphics +import Foundation + +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 + } + + override public init() { + super.init() + } + + override public init(dataSets: [IChartDataSet]?) { + super.init(dataSets: dataSets) + } + + override open func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry? { + getDataSetByIndex(highlight.dataSetIndex)?.entryForIndex(Int(highlight.x)) + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift new file mode 100644 index 0000000..53babb9 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift @@ -0,0 +1,48 @@ +// +// 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 CoreGraphics +import Foundation + +open class RadarChartDataEntry: ChartDataEntry { + public required init() { + super.init() + } + + /// - Parameters: + /// - value: The value on the y-axis. + @objc public init(value: Double) { + super.init(x: .nan, y: value) + } + + /// - Parameters: + /// - value: The value on the y-axis. + /// - data: Spot for additional data this Entry represents. + @objc public convenience init(value: Double, data: Any?) { + self.init(value: value) + self.data = data + } + + // MARK: Data property accessors + + @objc open var value: Double { + get { y } + set { y = newValue } + } + + // MARK: NSCopying + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! RadarChartDataEntry + + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift new file mode 100644 index 0000000..ad5cbb2 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift @@ -0,0 +1,54 @@ +// +// 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 CoreGraphics +import Foundation + +open class RadarChartDataSet: LineRadarChartDataSet, IRadarChartDataSet { + private func initialize() { + valueFont = NSUIFont.systemFont(ofSize: 13.0) + } + + public required init() { + super.init() + initialize() + } + + override public required init(entries: [ChartDataEntry]?, label: String?) { + super.init(entries: entries, 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 + + /// `true` if highlight circle should be drawn, `false` ifnot + open var isDrawHighlightCircleEnabled: Bool { 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 +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartData.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartData.swift new file mode 100644 index 0000000..f5cc601 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartData.swift @@ -0,0 +1,30 @@ +// +// 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 CoreGraphics +import Foundation + +open class ScatterChartData: BarLineScatterCandleBubbleChartData { + override public init() { + super.init() + } + + override public init(dataSets: [IChartDataSet]?) { + super.init(dataSets: dataSets) + } + + /// - Returns: The maximum shape-size across all DataSets. + @objc open func getGreatestShapeSize() -> CGFloat { + (_dataSets as? [IScatterChartDataSet])? + .max { $0.scatterShapeSize < $1.scatterShapeSize }? + .scatterShapeSize ?? 0 + } +} diff --git a/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift new file mode 100644 index 0000000..35cd919 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift @@ -0,0 +1,71 @@ +// +// 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 CoreGraphics +import Foundation + +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? + + /// 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) { + 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 + + override open func copy(with zone: NSZone? = nil) -> Any { + let copy = super.copy(with: zone) as! ScatterChartDataSet + copy.scatterShapeSize = scatterShapeSize + copy.scatterShapeHoleRadius = scatterShapeHoleRadius + copy.scatterShapeHoleColor = scatterShapeHoleColor + copy.shapeRenderer = shapeRenderer + return copy + } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/IBarChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/IBarChartDataSet.swift new file mode 100644 index 0000000..a71d15c --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/IBarChartDataSet.swift @@ -0,0 +1,41 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol IBarChartDataSet: IBarLineScatterCandleBubbleChartDataSet { + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// `true` if this DataSet is stacked (stacksize > 1) or not. + var isStacked: Bool { get } + + /// 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 } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/IBarLineScatterCandleBubbleChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/IBarLineScatterCandleBubbleChartDataSet.swift new file mode 100644 index 0000000..68915d5 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/IBarLineScatterCandleBubbleChartDataSet.swift @@ -0,0 +1,25 @@ +// +// 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 CoreGraphics +import Foundation + +@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 } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/IBubbleChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/IBubbleChartDataSet.swift new file mode 100644 index 0000000..a731d08 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/IBubbleChartDataSet.swift @@ -0,0 +1,26 @@ +// +// 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 CoreGraphics +import Foundation + +@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 } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/ICandleChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/ICandleChartDataSet.swift new file mode 100644 index 0000000..d8b333c --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/ICandleChartDataSet.swift @@ -0,0 +1,65 @@ +// +// 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 CoreGraphics +import Foundation + +@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 } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/IChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/IChartDataSet.swift new file mode 100644 index 0000000..f9013a9 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/IChartDataSet.swift @@ -0,0 +1,272 @@ +// +// 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 CoreGraphics +import Foundation + +@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) + + /// The minimum y-value this DataSet holds + var yMin: Double { get } + + /// The maximum y-value this DataSet holds + var yMax: Double { get } + + /// The minimum x-value this DataSet holds + var xMin: Double { get } + + /// The maximum x-value this DataSet holds + var xMax: Double { get } + + /// The number of y-values this DataSet represents + var entryCount: Int { get } + + /// - Throws: out of bounds + /// if `i` is out of bounds, it may throw an out-of-bounds exception + /// - Returns: The entry object found at the given index (not x-value!) + func entryForIndex(_ i: Int) -> ChartDataEntry? + + /// - Parameters: + /// - xValue: the x-value + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - rounding: determine whether to round up/down/closest if there is no Entry matching the provided x-value + /// - 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. + func entryForXValue( + _ xValue: Double, + closestToY yValue: Double, + rounding: ChartDataSetRounding + ) -> ChartDataEntry? + + /// - Parameters: + /// - xValue: the x-value + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - 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. + 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] + + /// - Parameters: + /// - xValue: x-value of the entry to search for + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - rounding: Rounding method if exact value was not found + /// - 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. + func entryIndex( + x xValue: Double, + closestToY yValue: Double, + rounding: ChartDataSetRounding + ) -> Int + + /// - Parameters: + /// - e: the entry to search for + /// - Returns: The array-index of the specified entry + 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. + /// + /// - Parameters: + /// - 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. + /// + /// - Parameters: + /// - 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* + /// + /// - Parameters: + /// - 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* + /// + /// - Parameters: + /// - 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* + /// + /// - Parameters: + /// - 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 } + + /// `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 } + + /// `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 } + + /// `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 } + + /// `true` if this DataSet is visible inside the chart, or `false` ifit is currently hidden. + var isVisible: Bool { get } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/ILineChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/ILineChartDataSet.swift new file mode 100644 index 0000000..b3b9fd0 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/ILineChartDataSet.swift @@ -0,0 +1,78 @@ +// +// 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 CoreGraphics +import Foundation + +@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 } + + /// `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 } + + /// `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 } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/ILineRadarChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/ILineRadarChartDataSet.swift new file mode 100644 index 0000000..b446fca --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/ILineRadarChartDataSet.swift @@ -0,0 +1,44 @@ +// +// 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 CoreGraphics +import Foundation + +@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 } + + /// `true` if filled drawing is enabled, `false` if not + var isDrawFilledEnabled: Bool { get } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift new file mode 100644 index 0000000..08f58d6 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift @@ -0,0 +1,35 @@ +// +// 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 } + + /// `true` if horizontal highlight indicator lines are enabled (drawn) + var isHorizontalHighlightIndicatorEnabled: Bool { get } + + /// `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) +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/IPieChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/IPieChartDataSet.swift new file mode 100644 index 0000000..6e1a252 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/IPieChartDataSet.swift @@ -0,0 +1,62 @@ +// +// 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 CoreGraphics +import Foundation + +@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 and enabled, line will have the same color as the slice + var useValueColorForLine: Bool { 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 } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/IRadarChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/IRadarChartDataSet.swift new file mode 100644 index 0000000..d786392 --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/IRadarChartDataSet.swift @@ -0,0 +1,39 @@ +// +// 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 CoreGraphics +import Foundation + +@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 } +} diff --git a/Pods/Charts/Source/Charts/Data/Interfaces/IScatterChartDataSet.swift b/Pods/Charts/Source/Charts/Data/Interfaces/IScatterChartDataSet.swift new file mode 100644 index 0000000..f1135ba --- /dev/null +++ b/Pods/Charts/Source/Charts/Data/Interfaces/IScatterChartDataSet.swift @@ -0,0 +1,35 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol IScatterChartDataSet: ILineScatterCandleRadarChartDataSet { + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// 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 } + + /// The IShapeRenderer responsible for rendering this DataSet. + var shapeRenderer: IShapeRenderer? { get } +} diff --git a/Pods/Charts/Source/Charts/Filters/DataApproximator+N.swift b/Pods/Charts/Source/Charts/Filters/DataApproximator+N.swift new file mode 100644 index 0000000..af6d47b --- /dev/null +++ b/Pods/Charts/Source/Charts/Filters/DataApproximator+N.swift @@ -0,0 +1,146 @@ +// +// DataApproximator+N.swift +// Charts +// +// Created by M Ivaniushchenko on 9/6/17. +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import CoreGraphics +import Foundation + +private extension CGPoint { + func distanceToLine(from linePoint1: CGPoint, to linePoint2: CGPoint) -> CGFloat { + let dx = linePoint2.x - linePoint1.x + let dy = linePoint2.y - linePoint1.y + + let dividend = abs(dy * x - dx * 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 { + index = i + self.distance = distance + } + } + } +} + +extension LineAlt: Comparable { + static func == (lhs: LineAlt, rhs: LineAlt) -> Bool { + (lhs.start == rhs.start) && (lhs.end == rhs.end) && (lhs.index == rhs.index) + } + + static func < (lhs: LineAlt, rhs: LineAlt) -> Bool { + 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 { + insertLine(left, into: &queue) + } + + let right = LineAlt(start: line.index, end: line.end, points: points) + if right.index > 0 { + 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 + 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 + } +} diff --git a/Pods/Charts/Source/Charts/Filters/DataApproximator.swift b/Pods/Charts/Source/Charts/Filters/DataApproximator.swift new file mode 100644 index 0000000..8ad8d78 --- /dev/null +++ b/Pods/Charts/Source/Charts/Filters/DataApproximator.swift @@ -0,0 +1,105 @@ +// +// 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 CoreGraphics +import Foundation + +@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 + return zip(keep, points).compactMap { $0 ? nil : $1 } + } + + /// apply the Douglas-Peucker-Reduction to an array of `CGPoint`s with a given tolerance + /// + /// - Parameters: + /// - points: + /// - tolerance: + /// - start: + /// - 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 { + abs(dy * pt.x - dx * pt.y + sxey - exsy) / length + } + } +} diff --git a/Pods/Charts/Source/Charts/Formatters/DefaultAxisValueFormatter.swift b/Pods/Charts/Source/Charts/Formatters/DefaultAxisValueFormatter.swift new file mode 100644 index 0000000..f396c29 --- /dev/null +++ b/Pods/Charts/Source/Charts/Formatters/DefaultAxisValueFormatter.swift @@ -0,0 +1,90 @@ +// +// 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 { _formatter } + set { + hasAutoDecimals = false + _formatter = newValue + } + } + + // TODO: Documentation. Especially the nil case + private var _decimals: Int? + open var decimals: Int? { + get { _decimals } + set { + _decimals = newValue + + if let digits = newValue { + self.formatter?.minimumFractionDigits = digits + self.formatter?.maximumFractionDigits = digits + self.formatter?.usesGroupingSeparator = true + } + } + } + + override public init() { + super.init() + + formatter = NumberFormatter() + hasAutoDecimals = true + } + + @objc public init(formatter: NumberFormatter) { + super.init() + + self.formatter = formatter + } + + @objc public init(decimals: Int) { + super.init() + + formatter = NumberFormatter() + 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? { + 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)) ?? "" + } + } +} diff --git a/Pods/Charts/Source/Charts/Formatters/DefaultFillFormatter.swift b/Pods/Charts/Source/Charts/Formatters/DefaultFillFormatter.swift new file mode 100644 index 0000000..c9af094 --- /dev/null +++ b/Pods/Charts/Source/Charts/Formatters/DefaultFillFormatter.swift @@ -0,0 +1,53 @@ +// +// 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 CoreGraphics +import Foundation + +/// 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? + + override public init() {} + + @objc public init(block: @escaping Block) { + self.block = block + } + + @objc public static func with(block: @escaping Block) -> DefaultFillFormatter? { + 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 + } +} diff --git a/Pods/Charts/Source/Charts/Formatters/DefaultValueFormatter.swift b/Pods/Charts/Source/Charts/Formatters/DefaultValueFormatter.swift new file mode 100644 index 0000000..7176b82 --- /dev/null +++ b/Pods/Charts/Source/Charts/Formatters/DefaultValueFormatter.swift @@ -0,0 +1,93 @@ +// +// 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 { _formatter } + set { + hasAutoDecimals = false + _formatter = newValue + } + } + + private var _decimals: Int? + open var decimals: Int? { + get { _decimals } + set { + _decimals = newValue + + if let digits = newValue { + self.formatter?.minimumFractionDigits = digits + self.formatter?.maximumFractionDigits = digits + self.formatter?.usesGroupingSeparator = true + } + } + } + + override public init() { + super.init() + + formatter = NumberFormatter() + hasAutoDecimals = true + } + + @objc public init(formatter: NumberFormatter) { + super.init() + + self.formatter = formatter + } + + @objc public init(decimals: Int) { + super.init() + + formatter = NumberFormatter() + 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? { + 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)) ?? "" + } + } +} diff --git a/Pods/Charts/Source/Charts/Formatters/IAxisValueFormatter.swift b/Pods/Charts/Source/Charts/Formatters/IAxisValueFormatter.swift new file mode 100644 index 0000000..9e109ed --- /dev/null +++ b/Pods/Charts/Source/Charts/Formatters/IAxisValueFormatter.swift @@ -0,0 +1,27 @@ +// +// 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: AnyObject { + /// Called when a value from an axis is formatted before being drawn. + /// + /// For performance reasons, avoid excessive calculations and memory allocations inside this method. + /// + /// - Parameters: + /// - value: the value that is currently being drawn + /// - axis: the axis that the value belongs to + /// - Returns: The customized label that is drawn on the x-axis. + func stringForValue(_ value: Double, + axis: AxisBase?) -> String +} diff --git a/Pods/Charts/Source/Charts/Formatters/IFillFormatter.swift b/Pods/Charts/Source/Charts/Formatters/IFillFormatter.swift new file mode 100644 index 0000000..9459296 --- /dev/null +++ b/Pods/Charts/Source/Charts/Formatters/IFillFormatter.swift @@ -0,0 +1,20 @@ +// +// 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 CoreGraphics +import Foundation + +/// 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 +} diff --git a/Pods/Charts/Source/Charts/Formatters/IValueFormatter.swift b/Pods/Charts/Source/Charts/Formatters/IValueFormatter.swift new file mode 100644 index 0000000..a6a88f9 --- /dev/null +++ b/Pods/Charts/Source/Charts/Formatters/IValueFormatter.swift @@ -0,0 +1,34 @@ +// +// 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 drawn to the screen. +/// +/// Simply create your own formatting class and let it implement ValueFormatter. Then override the stringForValue() +/// method and return whatever you want. + +@objc(IChartValueFormatter) +public protocol IValueFormatter: AnyObject { + /// 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. + /// + /// - Parameters: + /// - value: The value to be formatted + /// - dataSetIndex: The index of the DataSet the entry in focus belongs to + /// - viewPortHandler: provides information about the current chart state (scale, translation, ...) + /// - Returns: The formatted label ready to be drawn + func stringForValue(_ value: Double, + entry: ChartDataEntry, + dataSetIndex: Int, + viewPortHandler: ViewPortHandler?) -> String +} diff --git a/Pods/Charts/Source/Charts/Formatters/IndexAxisValueFormatter.swift b/Pods/Charts/Source/Charts/Formatters/IndexAxisValueFormatter.swift new file mode 100644 index 0000000..46e480a --- /dev/null +++ b/Pods/Charts/Source/Charts/Formatters/IndexAxisValueFormatter.swift @@ -0,0 +1,51 @@ +// +// 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]() + private var _valueCount: Int = 0 + + @objc public var values: [String] { + get { + _values + } + set { + _values = newValue + _valueCount = _values.count + } + } + + override public init() { + super.init() + } + + @objc public init(values: [String]) { + super.init() + + self.values = values + } + + @objc public static func with(values: [String]) -> IndexAxisValueFormatter? { + 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] + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/BarHighlighter.swift b/Pods/Charts/Source/Charts/Highlight/BarHighlighter.swift new file mode 100644 index 0000000..9d278b1 --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/BarHighlighter.swift @@ -0,0 +1,100 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(BarChartHighlighter) +open class BarHighlighter: ChartHighlighter { + override open func getHighlight(x: CGFloat, y: CGFloat) -> Highlight? { + guard + let barData = (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 + } + } + + override internal func getDistance(x1: CGFloat, y1 _: CGFloat, x2: CGFloat, y2 _: CGFloat) -> CGFloat { + abs(x1 - x2) + } + + override internal var data: ChartData? { + (chart as? BarChartDataProvider)?.barData + } + + /// This method creates the Highlight object that also indicates which value of a stacked BarEntry has been selected. + /// + /// - Parameters: + /// - high: the Highlight to work with looking for stacked values + /// - set: + /// - xIndex: + /// - 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) + } + + /// - Parameters: + /// - entry: + /// - value: + /// - Returns: The index of the closest value inside the values array / ranges (stacked barchart) to the value given as a parameter. + @objc open func getClosestStackIndex(ranges: [Range]?, value: Double) -> Int { + guard let ranges = ranges else { return 0 } + if let stackIndex = ranges.firstIndex(where: { $0.contains(value) }) { + return stackIndex + } else { + let length = max(ranges.count - 1, 0) + return (value > ranges[length].to) ? length : 0 + } + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/ChartHighlighter.swift b/Pods/Charts/Source/Charts/Highlight/ChartHighlighter.swift new file mode 100644 index 0000000..55d0d0c --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/ChartHighlighter.swift @@ -0,0 +1,163 @@ +// +// 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 CoreGraphics +import Foundation + +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) + } + + /// - Parameters: + /// - x: + /// - Returns: The corresponding x-pos for a given touch-position in pixels. + @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) + } + + /// - Parameters: + /// - xValue: + /// - x: + /// - y: + /// - Returns: The corresponding ChartHighlight for a given x-value and xy-touch position in pixels. + @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 + } + + /// - Parameters: + /// - xValue: the transformed x-value of the x-touch position + /// - x: touch position + /// - y: touch position + /// - 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). + @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] { + guard let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider else { return [] } + + 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) + } + + return entries.map { e in + let px = chart.getTransformer(forAxis: set.axisDependency) + .pixelForValues(x: e.x, y: e.y) + + return Highlight(x: e.x, y: e.y, xPx: px.x, yPx: px.y, dataSetIndex: dataSetIndex, axis: set.axisDependency) + } + } + + // - 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 where high.axis == axis { + let tempDistance = abs(getHighlightPos(high: high) - y) + if tempDistance < distance { + distance = tempDistance + } + } + + return distance + } + + internal func getHighlightPos(high: Highlight) -> CGFloat { + high.yPx + } + + internal func getDistance(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) -> CGFloat { + hypot(x1 - x2, y1 - y2) + } + + internal var data: ChartData? { + chart?.data + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/CombinedHighlighter.swift b/Pods/Charts/Source/Charts/Highlight/CombinedHighlighter.swift new file mode 100644 index 0000000..eb417cb --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/CombinedHighlighter.swift @@ -0,0 +1,62 @@ +// +// 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 CoreGraphics +import Foundation + +@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 + barHighlighter = barDataProvider.barData == nil ? nil : BarHighlighter(chart: barDataProvider) + } + + override open 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 + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/Highlight.swift b/Pods/Charts/Source/Charts/Highlight/Highlight.swift new file mode 100644 index 0000000..50562bc --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/Highlight.swift @@ -0,0 +1,195 @@ +// +// 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 CoreGraphics +import Foundation + +@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.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 + + override public init() { + super.init() + } + + /// - Parameters: + /// - x: the x-value of the highlighted value + /// - y: the y-value of the highlighted value + /// - xPx: the x-pixel of the highlighted value + /// - yPx: the y-pixel of the highlighted value + /// - dataIndex: the index of the Data the highlighted value belongs to + /// - dataSetIndex: the index of the DataSet the highlighted value belongs to + /// - stackIndex: references which value of a stacked-bar entry has been selected + /// - 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 + } + + /// - Parameters: + /// - x: the x-value of the highlighted value + /// - y: the y-value of the highlighted value + /// - xPx: the x-pixel of the highlighted value + /// - yPx: the y-pixel of the highlighted value + /// - dataSetIndex: the index of the DataSet the highlighted value belongs to + /// - stackIndex: references which value of a stacked-bar entry has been selected + /// - 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) + } + + /// - Parameters: + /// - x: the x-value of the highlighted value + /// - y: the y-value of the highlighted value + /// - xPx: the x-pixel of the highlighted value + /// - yPx: the y-pixel of the highlighted value + /// - dataIndex: the index of the Data the highlighted value belongs to + /// - dataSetIndex: the index of the DataSet the highlighted value belongs to + /// - stackIndex: references which value of a stacked-bar entry has been selected + /// - 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 + } + + /// - Parameters: + /// - x: the x-value of the highlighted value + /// - y: the y-value of the highlighted value + /// - dataSetIndex: the index of the DataSet the highlighted value belongs to + /// - 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 + } + + /// - Parameters: + /// - x: the x-value of the highlighted value + /// - dataSetIndex: the index of the DataSet the highlighted value belongs to + /// - 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 { _x } + @objc open var y: Double { _y } + @objc open var xPx: CGFloat { _xPx } + @objc open var yPx: CGFloat { _yPx } + @objc open var dataSetIndex: Int { _dataSetIndex } + @objc open var stackIndex: Int { _stackIndex } + @objc open var axis: YAxis.AxisDependency { _axis } + + @objc open var isStacked: Bool { _stackIndex >= 0 } + + /// Sets the x- and y-position (pixels) where this highlight was last drawn. + @objc open func setDraw(x: CGFloat, y: CGFloat) { + drawX = x + drawY = y + } + + /// Sets the x- and y-position (pixels) where this highlight was last drawn. + @objc open func setDraw(pt: CGPoint) { + drawX = pt.x + drawY = pt.y + } + + // MARK: NSObject + + override open var description: String { + "Highlight, x: \(_x), y: \(_y), dataIndex (combined charts): \(dataIndex), dataSetIndex: \(_dataSetIndex), stackIndex (only stacked barentry): \(_stackIndex)" + } +} + +// MARK: Equatable + +extension Highlight /*: Equatable */ { + override open 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 + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/HorizontalBarHighlighter.swift b/Pods/Charts/Source/Charts/Highlight/HorizontalBarHighlighter.swift new file mode 100644 index 0000000..2d0e838 --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/HorizontalBarHighlighter.swift @@ -0,0 +1,59 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(HorizontalBarChartHighlighter) +open class HorizontalBarHighlighter: BarHighlighter { + override open func getHighlight(x: CGFloat, y: CGFloat) -> Highlight? { + guard let barData = 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 + } + + override internal func buildHighlights( + dataSet set: IChartDataSet, + dataSetIndex: Int, + xValue: Double, + rounding: ChartDataSetRounding + ) -> [Highlight] { + guard let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider else { return [] } + + 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) + } + + return entries.map { e in + let px = chart.getTransformer(forAxis: set.axisDependency) + .pixelForValues(x: e.y, y: e.x) + return Highlight(x: e.x, y: e.y, xPx: px.x, yPx: px.y, dataSetIndex: dataSetIndex, axis: set.axisDependency) + } + } + + override internal func getDistance(x1 _: CGFloat, y1: CGFloat, x2 _: CGFloat, y2: CGFloat) -> CGFloat { + abs(y1 - y2) + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/IHighlighter.swift b/Pods/Charts/Source/Charts/Highlight/IHighlighter.swift new file mode 100644 index 0000000..b09f5fa --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/IHighlighter.swift @@ -0,0 +1,22 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(IChartHighlighter) +public protocol IHighlighter: AnyObject { + /// - Parameters: + /// - x: + /// - y: + /// - Returns: A Highlight object corresponding to the given x- and y- touch positions in pixels. + func getHighlight(x: CGFloat, y: CGFloat) -> Highlight? +} diff --git a/Pods/Charts/Source/Charts/Highlight/PieHighlighter.swift b/Pods/Charts/Source/Charts/Highlight/PieHighlighter.swift new file mode 100644 index 0000000..467ebe7 --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/PieHighlighter.swift @@ -0,0 +1,25 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(PieChartHighlighter) +open class PieHighlighter: PieRadarHighlighter { + override open 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) + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/PieRadarHighlighter.swift b/Pods/Charts/Source/Charts/Highlight/PieRadarHighlighter.swift new file mode 100644 index 0000000..85c019f --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/PieRadarHighlighter.swift @@ -0,0 +1,52 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(PieRadarChartHighlighter) +open class PieRadarHighlighter: ChartHighlighter { + override open 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) + } + } + + /// - Parameters: + /// - index: + /// - x: + /// - y: + /// - Returns: The closest Highlight object of the given objects based on the touch position inside the chart. + @objc open func closestHighlight(index _: Int, x _: CGFloat, y _: CGFloat) -> Highlight? { + fatalError("closestHighlight(index, x, y) cannot be called on PieRadarChartHighlighter") + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/RadarHighlighter.swift b/Pods/Charts/Source/Charts/Highlight/RadarHighlighter.swift new file mode 100644 index 0000000..5e0f294 --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/RadarHighlighter.swift @@ -0,0 +1,72 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(RadarChartHighlighter) +open class RadarHighlighter: PieRadarHighlighter { + override open 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 + } + + /// - Parameters: + /// - index: + /// - 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. + 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 + } +} diff --git a/Pods/Charts/Source/Charts/Highlight/Range.swift b/Pods/Charts/Source/Charts/Highlight/Range.swift new file mode 100644 index 0000000..8ad7c81 --- /dev/null +++ b/Pods/Charts/Source/Charts/Highlight/Range.swift @@ -0,0 +1,44 @@ +// +// 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() + } + + /// - Parameters: + /// - value: + /// - Returns: `true` if this range contains (if the value is in between) the given value, `false` ifnot. + @objc open func contains(_ value: Double) -> Bool { + if value > from, value <= to { + return true + } else { + return false + } + } + + @objc open func isLarger(_ value: Double) -> Bool { + value > to + } + + @objc open func isSmaller(_ value: Double) -> Bool { + value < from + } +} diff --git a/Pods/Charts/Source/Charts/Interfaces/BarChartDataProvider.swift b/Pods/Charts/Source/Charts/Interfaces/BarChartDataProvider.swift new file mode 100644 index 0000000..c59d3dc --- /dev/null +++ b/Pods/Charts/Source/Charts/Interfaces/BarChartDataProvider.swift @@ -0,0 +1,22 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol BarChartDataProvider: BarLineScatterCandleBubbleChartDataProvider { + var barData: BarChartData? { get } + + var isDrawBarShadowEnabled: Bool { get } + var isDrawValueAboveBarEnabled: Bool { get } + var isHighlightFullBarEnabled: Bool { get } +} diff --git a/Pods/Charts/Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift b/Pods/Charts/Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift new file mode 100644 index 0000000..1883cc6 --- /dev/null +++ b/Pods/Charts/Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift @@ -0,0 +1,22 @@ +// +// 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 CoreGraphics +import Foundation + +@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 } +} diff --git a/Pods/Charts/Source/Charts/Interfaces/BubbleChartDataProvider.swift b/Pods/Charts/Source/Charts/Interfaces/BubbleChartDataProvider.swift new file mode 100644 index 0000000..ed9bca4 --- /dev/null +++ b/Pods/Charts/Source/Charts/Interfaces/BubbleChartDataProvider.swift @@ -0,0 +1,18 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol BubbleChartDataProvider: BarLineScatterCandleBubbleChartDataProvider { + var bubbleData: BubbleChartData? { get } +} diff --git a/Pods/Charts/Source/Charts/Interfaces/CandleChartDataProvider.swift b/Pods/Charts/Source/Charts/Interfaces/CandleChartDataProvider.swift new file mode 100644 index 0000000..fdbd69c --- /dev/null +++ b/Pods/Charts/Source/Charts/Interfaces/CandleChartDataProvider.swift @@ -0,0 +1,18 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol CandleChartDataProvider: BarLineScatterCandleBubbleChartDataProvider { + var candleData: CandleChartData? { get } +} diff --git a/Pods/Charts/Source/Charts/Interfaces/ChartDataProvider.swift b/Pods/Charts/Source/Charts/Interfaces/ChartDataProvider.swift new file mode 100644 index 0000000..f3f4ff3 --- /dev/null +++ b/Pods/Charts/Source/Charts/Interfaces/ChartDataProvider.swift @@ -0,0 +1,38 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol ChartDataProvider { + /// The minimum x-value of the chart, regardless of zoom or translation. + var chartXMin: Double { get } + + /// The maximum x-value of the chart, regardless of zoom or translation. + var chartXMax: Double { get } + + /// The minimum y-value of the chart, regardless of zoom or translation. + var chartYMin: Double { get } + + /// 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 } +} diff --git a/Pods/Charts/Source/Charts/Interfaces/CombinedChartDataProvider.swift b/Pods/Charts/Source/Charts/Interfaces/CombinedChartDataProvider.swift new file mode 100644 index 0000000..19c9f21 --- /dev/null +++ b/Pods/Charts/Source/Charts/Interfaces/CombinedChartDataProvider.swift @@ -0,0 +1,18 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol CombinedChartDataProvider: LineChartDataProvider, BarChartDataProvider, BubbleChartDataProvider, CandleChartDataProvider, ScatterChartDataProvider { + var combinedData: CombinedChartData? { get } +} diff --git a/Pods/Charts/Source/Charts/Interfaces/LineChartDataProvider.swift b/Pods/Charts/Source/Charts/Interfaces/LineChartDataProvider.swift new file mode 100644 index 0000000..e24fdb0 --- /dev/null +++ b/Pods/Charts/Source/Charts/Interfaces/LineChartDataProvider.swift @@ -0,0 +1,20 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol LineChartDataProvider: BarLineScatterCandleBubbleChartDataProvider { + var lineData: LineChartData? { get } + + func getAxis(_ axis: YAxis.AxisDependency) -> YAxis +} diff --git a/Pods/Charts/Source/Charts/Interfaces/ScatterChartDataProvider.swift b/Pods/Charts/Source/Charts/Interfaces/ScatterChartDataProvider.swift new file mode 100644 index 0000000..7ffed21 --- /dev/null +++ b/Pods/Charts/Source/Charts/Interfaces/ScatterChartDataProvider.swift @@ -0,0 +1,18 @@ +// +// 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 CoreGraphics +import Foundation + +@objc +public protocol ScatterChartDataProvider: BarLineScatterCandleBubbleChartDataProvider { + var scatterData: ScatterChartData? { get } +} diff --git a/Pods/Charts/Source/Charts/Jobs/AnimatedMoveViewJob.swift b/Pods/Charts/Source/Charts/Jobs/AnimatedMoveViewJob.swift new file mode 100644 index 0000000..f4cc36f --- /dev/null +++ b/Pods/Charts/Source/Charts/Jobs/AnimatedMoveViewJob.swift @@ -0,0 +1,31 @@ +// +// 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 CoreGraphics +import Foundation + +open class AnimatedMoveViewJob: AnimatedViewPortJob { + override internal 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) + } +} diff --git a/Pods/Charts/Source/Charts/Jobs/AnimatedViewPortJob.swift b/Pods/Charts/Source/Charts/Jobs/AnimatedViewPortJob.swift new file mode 100644 index 0000000..244b346 --- /dev/null +++ b/Pods/Charts/Source/Charts/Jobs/AnimatedViewPortJob.swift @@ -0,0 +1,115 @@ +// +// 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 CoreGraphics +import Foundation +import QuartzCore + +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 + _duration = duration + _easing = easing + } + + deinit { + stop(finish: false) + } + + override open 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: RunLoop.Mode.common) + } + + @objc open func stop(finish: Bool) { + guard _displayLink != nil else { return } + + _displayLink.remove(from: .main, forMode: RunLoop.Mode.common) + _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 + } +} diff --git a/Pods/Charts/Source/Charts/Jobs/AnimatedZoomViewJob.swift b/Pods/Charts/Source/Charts/Jobs/AnimatedZoomViewJob.swift new file mode 100644 index 0000000..56d9ce0 --- /dev/null +++ b/Pods/Charts/Source/Charts/Jobs/AnimatedZoomViewJob.swift @@ -0,0 +1,93 @@ +// +// 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 CoreGraphics +import Foundation + +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 + } + + override internal 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) + } + + override internal func animationEnd() { + (view as? BarLineChartViewBase)?.calculateOffsets() + view?.setNeedsDisplay() + } +} diff --git a/Pods/Charts/Source/Charts/Jobs/MoveViewJob.swift b/Pods/Charts/Source/Charts/Jobs/MoveViewJob.swift new file mode 100644 index 0000000..455926a --- /dev/null +++ b/Pods/Charts/Source/Charts/Jobs/MoveViewJob.swift @@ -0,0 +1,32 @@ +// +// 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 CoreGraphics +import Foundation + +@objc(MoveChartViewJob) +open class MoveViewJob: ViewPortJob { + override open 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) + } +} diff --git a/Pods/Charts/Source/Charts/Jobs/ViewPortJob.swift b/Pods/Charts/Source/Charts/Jobs/ViewPortJob.swift new file mode 100644 index 0000000..a48d6e2 --- /dev/null +++ b/Pods/Charts/Source/Charts/Jobs/ViewPortJob.swift @@ -0,0 +1,44 @@ +// +// ViewPortJob.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 CoreGraphics +import Foundation + +// This defines a viewport modification job, used for delaying or animating viewport changes +@objc(ChartViewPortJob) +open class ViewPortJob: NSObject { + internal var point = CGPoint() + internal weak var viewPortHandler: ViewPortHandler? + internal var xValue: Double = 0.0 + internal var yValue: Double = 0.0 + internal weak var transformer: Transformer? + internal weak var view: ChartViewBase? + + @objc public init( + viewPortHandler: ViewPortHandler, + xValue: Double, + yValue: Double, + transformer: Transformer, + view: ChartViewBase + ) { + super.init() + + self.viewPortHandler = viewPortHandler + self.xValue = xValue + self.yValue = yValue + self.transformer = transformer + self.view = view + } + + @objc open func doJob() { + fatalError("`doJob()` must be overridden by subclasses") + } +} diff --git a/Pods/Charts/Source/Charts/Jobs/ZoomViewJob.swift b/Pods/Charts/Source/Charts/Jobs/ZoomViewJob.swift new file mode 100644 index 0000000..7292cc3 --- /dev/null +++ b/Pods/Charts/Source/Charts/Jobs/ZoomViewJob.swift @@ -0,0 +1,70 @@ +// +// ZoomViewJob.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 CoreGraphics +import Foundation + +@objc(ZoomChartViewJob) +open class ZoomViewJob: ViewPortJob { + internal var scaleX: CGFloat = 0.0 + internal var scaleY: CGFloat = 0.0 + internal var axisDependency: YAxis.AxisDependency = .left + + @objc public init( + viewPortHandler: ViewPortHandler, + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + transformer: Transformer, + axis: YAxis.AxisDependency, + view: ChartViewBase + ) { + super.init( + viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: yValue, + transformer: transformer, + view: view + ) + + self.scaleX = scaleX + self.scaleY = scaleY + axisDependency = axis + } + + override open func doJob() { + guard + let viewPortHandler = viewPortHandler, + let transformer = transformer, + let view = view + else { return } + + var matrix = viewPortHandler.setZoom(scaleX: scaleX, scaleY: scaleY) + viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: false) + + let yValsInView = (view as! BarLineChartViewBase).getAxis(axisDependency).axisRange / Double(viewPortHandler.scaleY) + let xValsInView = (view as! BarLineChartViewBase).xAxis.axisRange / Double(viewPortHandler.scaleX) + + var pt = CGPoint( + x: CGFloat(xValue - xValsInView / 2.0), + y: CGFloat(yValue + yValsInView / 2.0) + ) + + transformer.pointValueToPixel(&pt) + + matrix = viewPortHandler.translate(pt: pt) + viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: false) + + (view as! BarLineChartViewBase).calculateOffsets() + view.setNeedsDisplay() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/AxisRendererBase.swift b/Pods/Charts/Source/Charts/Renderers/AxisRendererBase.swift new file mode 100644 index 0000000..165530f --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/AxisRendererBase.swift @@ -0,0 +1,186 @@ +// +// AxisRendererBase.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 CoreGraphics +import Foundation + +@objc(ChartAxisRendererBase) +open class AxisRendererBase: Renderer { + /// base axis this axis renderer works with + @objc open var axis: AxisBase? + + /// transformer to transform values to screen pixels and return + @objc open var transformer: Transformer? + + @objc public init(viewPortHandler: ViewPortHandler, transformer: Transformer?, axis: AxisBase?) { + super.init(viewPortHandler: viewPortHandler) + + self.transformer = transformer + self.axis = axis + } + + /// Draws the axis labels on the specified context + @objc open func renderAxisLabels(context _: CGContext) { + fatalError("renderAxisLabels() cannot be called on AxisRendererBase") + } + + /// Draws the grid lines belonging to the axis. + @objc open func renderGridLines(context _: CGContext) { + fatalError("renderGridLines() cannot be called on AxisRendererBase") + } + + /// Draws the line that goes alongside the axis. + @objc open func renderAxisLine(context _: CGContext) { + fatalError("renderAxisLine() cannot be called on AxisRendererBase") + } + + /// Draws the LimitLines associated with this axis to the screen. + @objc open func renderLimitLines(context _: CGContext) { + fatalError("renderLimitLines() cannot be called on AxisRendererBase") + } + + /// Computes the axis values. + /// + /// - Parameters: + /// - min: the minimum value in the data object for this axis + /// - max: the maximum value in the data object for this axis + @objc open func computeAxis(min: Double, max: Double, inverted: Bool) { + var min = min, max = max + + if let transformer = self.transformer { + // calculate the starting and entry point of the y-labels (depending on zoom / contentrect bounds) + if viewPortHandler.contentWidth > 10.0, !viewPortHandler.isFullyZoomedOutY { + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + + if !inverted { + min = Double(p2.y) + max = Double(p1.y) + } else { + min = Double(p1.y) + max = Double(p2.y) + } + } + } + + computeAxisValues(min: min, max: max) + } + + /// Sets up the axis values. Computes the desired number of labels between the two given extremes. + @objc open func computeAxisValues(min: Double, max: Double) { + guard let axis = self.axis else { return } + + let yMin = min + let yMax = max + + let labelCount = axis.labelCount + let range = abs(yMax - yMin) + + if labelCount == 0 || range <= 0 || range.isInfinite { + axis.entries = [Double]() + axis.centeredEntries = [Double]() + return + } + + // Find out how much spacing (in y value space) between axis values + let rawInterval = range / Double(labelCount) + var interval = rawInterval.roundedToNextSignficant() + + // If granularity is enabled, then do not allow the interval to go below specified granularity. + // This is used to avoid repeated values when rounding values for display. + if axis.granularityEnabled { + interval = interval < axis.granularity ? axis.granularity : interval + } + + // Normalize interval + let intervalMagnitude = pow(10.0, Double(Int(log10(interval)))).roundedToNextSignficant() + let intervalSigDigit = Int(interval / intervalMagnitude) + if intervalSigDigit > 5 { + // Use one order of magnitude higher, to avoid intervals like 0.9 or 90 + // if it's 0.0 after floor(), we use the old value + interval = floor(10.0 * intervalMagnitude) == 0.0 ? interval : floor(10.0 * intervalMagnitude) + } + + var n = axis.centerAxisLabelsEnabled ? 1 : 0 + + // force label count + if axis.isForceLabelsEnabled { + interval = Double(range) / Double(labelCount - 1) + + // Ensure stops contains at least n elements. + axis.entries.removeAll(keepingCapacity: true) + axis.entries.reserveCapacity(labelCount) + + var v = yMin + + for _ in 0 ..< labelCount { + axis.entries.append(v) + v += interval + } + + n = labelCount + } else { + // no forced count + + var first = interval == 0.0 ? 0.0 : ceil(yMin / interval) * interval + + if axis.centerAxisLabelsEnabled { + first -= interval + } + + let last = interval == 0.0 ? 0.0 : (floor(yMax / interval) * interval).nextUp + + if interval != 0.0, last != first { + for _ in stride(from: first, through: last, by: interval) { + n += 1 + } + } else if last == first, n == 0 { + n = 1 + } + + // Ensure stops contains at least n elements. + axis.entries.removeAll(keepingCapacity: true) + axis.entries.reserveCapacity(labelCount) + + var f = first + var i = 0 + while i < n { + if f == 0.0 { + // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) + f = 0.0 + } + + axis.entries.append(Double(f)) + + f += interval + i += 1 + } + } + + // set decimals + if interval < 1 { + axis.decimals = Int(ceil(-log10(interval))) + } else { + axis.decimals = 0 + } + + if axis.centerAxisLabelsEnabled { + axis.centeredEntries.reserveCapacity(n) + axis.centeredEntries.removeAll() + + let offset: Double = interval / 2.0 + + for i in 0 ..< n { + axis.centeredEntries.append(axis.entries[i] + offset) + } + } + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/BarChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/BarChartRenderer.swift new file mode 100644 index 0000000..ebace5a --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/BarChartRenderer.swift @@ -0,0 +1,815 @@ +// +// BarChartRenderer.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +open class BarChartRenderer: BarLineScatterCandleBubbleRenderer { + /// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver + /// + /// Its use is apparent when there are multiple data sets, since we want to read bars in left to right order, + /// irrespective of dataset. However, drawing is done per dataset, so using this array and then flattening it prevents us from needing to + /// re-render for the sake of accessibility. + /// + /// In practise, its structure is: + /// + /// ```` + /// [ + /// [dataset1 element1, dataset2 element1], + /// [dataset1 element2, dataset2 element2], + /// [dataset1 element3, dataset2 element3] + /// ... + /// ] + /// ```` + /// This is done to provide numerical inference across datasets to a screenreader user, in the same way that a sighted individual + /// uses a multi-dataset bar chart. + /// + /// The ````internal```` specifier is to allow subclasses (HorizontalBar) to populate the same array + internal lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements() + + private class Buffer { + var rects = [CGRect]() + } + + @objc open weak var dataProvider: BarChartDataProvider? + + @objc public init(dataProvider: BarChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + // [CGRect] per dataset + private var _buffers = [Buffer]() + + override open func initBuffers() { + if let barData = dataProvider?.barData { + // Matche buffers count to dataset count + if _buffers.count != barData.dataSetCount { + while _buffers.count < barData.dataSetCount { + _buffers.append(Buffer()) + } + while _buffers.count > barData.dataSetCount { + _buffers.removeLast() + } + } + + for i in stride(from: 0, to: barData.dataSetCount, by: 1) { + let set = barData.dataSets[i] as! IBarChartDataSet + let size = set.entryCount * (set.isStacked ? set.stackSize : 1) + if _buffers[i].rects.count != size { + _buffers[i].rects = [CGRect](repeating: CGRect(), count: size) + } + } + } else { + _buffers.removeAll() + } + } + + private func prepareBuffer(dataSet: IBarChartDataSet, index: Int) { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + let barWidthHalf = barData.barWidth / 2.0 + + let buffer = _buffers[index] + var bufferIndex = 0 + let containsStacks = dataSet.isStacked + + let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) + let phaseY = animator.phaseY + var barRect = CGRect() + var x: Double + var y: Double + + for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) { + guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } + + let vals = e.yValues + + x = e.x + y = e.y + + if !containsStacks || vals == nil { + let left = CGFloat(x - barWidthHalf) + let right = CGFloat(x + barWidthHalf) + var top = isInverted + ? (y <= 0.0 ? CGFloat(y) : 0) + : (y >= 0.0 ? CGFloat(y) : 0) + var bottom = isInverted + ? (y >= 0.0 ? CGFloat(y) : 0) + : (y <= 0.0 ? CGFloat(y) : 0) + + /* When drawing each bar, the renderer actually draws each bar from 0 to the required value. + * This drawn bar is then clipped to the visible chart rect in BarLineChartViewBase's draw(rect:) using clipDataToContent. + * While this works fine when calculating the bar rects for drawing, it causes the accessibilityFrames to be oversized in some cases. + * This offset attempts to undo that unnecessary drawing when calculating barRects + * + * +---------------------------------------------------------------+---------------------------------------------------------------+ + * | Situation 1: (!inverted && y >= 0) | Situation 3: (inverted && y >= 0) | + * | | | + * | y -> +--+ <- top | 0 -> ---+--+---+--+------ <- top | + * | |//| } topOffset = y - max | | | |//| } topOffset = min | + * | max -> +---------+--+----+ <- top - topOffset | min -> +--+--+---+--+----+ <- top + topOffset | + * | | +--+ |//| | | | | | |//| | | + * | | | | |//| | | | +--+ |//| | | + * | | | | |//| | | | |//| | | + * | min -> +--+--+---+--+----+ <- bottom + bottomOffset | max -> +---------+--+----+ <- bottom - bottomOffset | + * | | | |//| } bottomOffset = min | |//| } bottomOffset = y - max | + * | 0 -> ---+--+---+--+----- <- bottom | y -> +--+ <- bottom | + * | | | + * +---------------------------------------------------------------+---------------------------------------------------------------+ + * | Situation 2: (!inverted && y < 0) | Situation 4: (inverted && y < 0) | + * | | | + * | 0 -> ---+--+---+--+----- <- top | y -> +--+ <- top | + * | | | |//| } topOffset = -max | |//| } topOffset = min - y | + * | max -> +--+--+---+--+----+ <- top - topOffset | min -> +---------+--+----+ <- top + topOffset | + * | | | | |//| | | | +--+ |//| | | + * | | +--+ |//| | | | | | |//| | | + * | | |//| | | | | | |//| | | + * | min -> +---------+--+----+ <- bottom + bottomOffset | max -> +--+--+---+--+----+ <- bottom - bottomOffset | + * | |//| } bottomOffset = min - y | | | |//| } bottomOffset = -max | + * | y -> +--+ <- bottom | 0 -> ---+--+---+--+------- <- bottom | + * | | | + * +---------------------------------------------------------------+---------------------------------------------------------------+ + */ + var topOffset: CGFloat = 0.0 + var bottomOffset: CGFloat = 0.0 + if let offsetView = dataProvider as? BarChartView { + let offsetAxis = offsetView.getAxis(dataSet.axisDependency) + if y >= 0 { + // situation 1 + if offsetAxis.axisMaximum < y { + topOffset = CGFloat(y - offsetAxis.axisMaximum) + } + if offsetAxis.axisMinimum > 0 { + bottomOffset = CGFloat(offsetAxis.axisMinimum) + } + } + else // y < 0 + { + // situation 2 + if offsetAxis.axisMaximum < 0 { + topOffset = CGFloat(offsetAxis.axisMaximum * -1) + } + if offsetAxis.axisMinimum > y { + bottomOffset = CGFloat(offsetAxis.axisMinimum - y) + } + } + if isInverted { + // situation 3 and 4 + // exchange topOffset/bottomOffset based on 1 and 2 + // see diagram above + (topOffset, bottomOffset) = (bottomOffset, topOffset) + } + } + // apply offset + top = isInverted ? top + topOffset : top - topOffset + bottom = isInverted ? bottom - bottomOffset : bottom + bottomOffset + + // multiply the height of the rect with the phase + // explicitly add 0 + topOffset to indicate this is changed after adding accessibility support (#3650, #3520) + if top > 0 + topOffset { + top *= CGFloat(phaseY) + } else { + bottom *= CGFloat(phaseY) + } + + barRect.origin.x = left + barRect.origin.y = top + barRect.size.width = right - left + barRect.size.height = bottom - top + buffer.rects[bufferIndex] = barRect + bufferIndex += 1 + } else { + var posY = 0.0 + var negY = -e.negativeSum + var yStart = 0.0 + + // fill the stack + for k in 0 ..< vals!.count { + let value = vals![k] + + if value == 0.0, posY == 0.0 || negY == 0.0 { + // Take care of the situation of a 0.0 value, which overlaps a non-zero bar + y = value + yStart = y + } else if value >= 0.0 { + y = posY + yStart = posY + value + posY = yStart + } else { + y = negY + yStart = negY + abs(value) + negY += abs(value) + } + + let left = CGFloat(x - barWidthHalf) + let right = CGFloat(x + barWidthHalf) + var top = isInverted + ? (y <= yStart ? CGFloat(y) : CGFloat(yStart)) + : (y >= yStart ? CGFloat(y) : CGFloat(yStart)) + var bottom = isInverted + ? (y >= yStart ? CGFloat(y) : CGFloat(yStart)) + : (y <= yStart ? CGFloat(y) : CGFloat(yStart)) + + // multiply the height of the rect with the phase + top *= CGFloat(phaseY) + bottom *= CGFloat(phaseY) + + barRect.origin.x = left + barRect.size.width = right - left + barRect.origin.y = top + barRect.size.height = bottom - top + + buffer.rects[bufferIndex] = barRect + bufferIndex += 1 + } + } + } + } + + override open func drawData(context: CGContext) { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() + + // Make the chart header the first element in the accessible elements array + if let chart = dataProvider as? BarChartView { + let element = createAccessibleHeader(usingChart: chart, + andData: barData, + withDefaultDescription: "Bar Chart") + accessibleChartElements.append(element) + } + + // Populate logically ordered nested elements into accessibilityOrderedElements in drawDataSet() + for i in 0 ..< barData.dataSetCount { + guard let set = barData.getDataSetByIndex(i) else { continue } + + if set.isVisible { + if !(set is IBarChartDataSet) { + fatalError("Datasets for BarChartRenderer must conform to IBarChartDataset") + } + + drawDataSet(context: context, dataSet: set as! IBarChartDataSet, index: i) + } + } + + // Merge nested ordered arrays into the single accessibleChartElements. + accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 }) + accessibilityPostLayoutChangedNotification() + } + + private var _barShadowRectBuffer = CGRect() + + @objc open func drawDataSet(context: CGContext, dataSet: IBarChartDataSet, index: Int) { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + prepareBuffer(dataSet: dataSet, index: index) + trans.rectValuesToPixel(&_buffers[index].rects) + + let borderWidth = dataSet.barBorderWidth + let borderColor = dataSet.barBorderColor + let drawBorder = borderWidth > 0.0 + + context.saveGState() + + // draw the bar shadow before the values + if dataProvider.isDrawBarShadowEnabled { + guard let barData = dataProvider.barData else { return } + + let barWidth = barData.barWidth + let barWidthHalf = barWidth / 2.0 + var x: Double = 0.0 + + for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) { + guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } + + x = e.x + + _barShadowRectBuffer.origin.x = CGFloat(x - barWidthHalf) + _barShadowRectBuffer.size.width = CGFloat(barWidth) + + trans.rectValueToPixel(&_barShadowRectBuffer) + + if !viewPortHandler.isInBoundsLeft(_barShadowRectBuffer.origin.x + _barShadowRectBuffer.size.width) { + continue + } + + if !viewPortHandler.isInBoundsRight(_barShadowRectBuffer.origin.x) { + break + } + + _barShadowRectBuffer.origin.y = viewPortHandler.contentTop + _barShadowRectBuffer.size.height = viewPortHandler.contentHeight + + context.setFillColor(dataSet.barShadowColor.cgColor) + context.fill(_barShadowRectBuffer) + } + } + + let buffer = _buffers[index] + + // draw the bar shadow before the values + if dataProvider.isDrawBarShadowEnabled { + for j in stride(from: 0, to: buffer.rects.count, by: 1) { + let barRect = buffer.rects[j] + + if !viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width) { + continue + } + + if !viewPortHandler.isInBoundsRight(barRect.origin.x) { + break + } + + context.setFillColor(dataSet.barShadowColor.cgColor) + context.fill(barRect) + } + } + + let isSingleColor = dataSet.colors.count == 1 + + if isSingleColor { + context.setFillColor(dataSet.color(atIndex: 0).cgColor) + } + + // In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements + let isStacked = dataSet.isStacked + let stackSize = isStacked ? dataSet.stackSize : 1 + + for j in stride(from: 0, to: buffer.rects.count, by: 1) { + let barRect = buffer.rects[j] + + if !viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width) { + continue + } + + if !viewPortHandler.isInBoundsRight(barRect.origin.x) { + break + } + + if !isSingleColor { + // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. + context.setFillColor(dataSet.color(atIndex: j).cgColor) + } + + context.fill(barRect) + + if drawBorder { + context.setStrokeColor(borderColor.cgColor) + context.setLineWidth(borderWidth) + context.stroke(barRect) + } + + // Create and append the corresponding accessibility element to accessibilityOrderedElements + if let chart = dataProvider as? BarChartView { + let element = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet, + dataSetIndex: index, + stackSize: stackSize) + { element in + element.accessibilityFrame = barRect + } + + accessibilityOrderedElements[j / stackSize].append(element) + } + } + + context.restoreGState() + } + + open func prepareBarHighlight( + x: Double, + y1: Double, + y2: Double, + barWidthHalf: Double, + trans: Transformer, + rect: inout CGRect + ) { + let left = x - barWidthHalf + let right = x + barWidthHalf + let top = y1 + let bottom = y2 + + rect.origin.x = CGFloat(left) + rect.origin.y = CGFloat(top) + rect.size.width = CGFloat(right - left) + rect.size.height = CGFloat(bottom - top) + + trans.rectValueToPixel(&rect, phaseY: animator.phaseY) + } + + override open func drawValues(context: CGContext) { + // if values are drawn + if isDrawingValuesAllowed(dataProvider: dataProvider) { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + let dataSets = barData.dataSets + + let valueOffsetPlus: CGFloat = 4.5 + var posOffset: CGFloat + var negOffset: CGFloat + let drawValueAboveBar = dataProvider.isDrawValueAboveBarEnabled + + for dataSetIndex in 0 ..< barData.dataSetCount { + guard let + dataSet = dataSets[dataSetIndex] as? IBarChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) + + // calculate the correct offset depending on the draw position of the value + let valueFont = dataSet.valueFont + let valueTextHeight = valueFont.lineHeight + posOffset = (drawValueAboveBar ? -(valueTextHeight + valueOffsetPlus) : valueOffsetPlus) + negOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextHeight + valueOffsetPlus)) + + if isInverted { + posOffset = -posOffset - valueTextHeight + negOffset = -negOffset - valueTextHeight + } + + let buffer = _buffers[dataSetIndex] + + guard let formatter = dataSet.valueFormatter else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + let iconsOffset = dataSet.iconsOffset + + // if only single values are drawn (sum) + if !dataSet.isStacked { + for j in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) { + guard let e = dataSet.entryForIndex(j) as? BarChartDataEntry else { continue } + + let rect = buffer.rects[j] + + let x = rect.origin.x + rect.size.width / 2.0 + + if !viewPortHandler.isInBoundsRight(x) { + break + } + + if !viewPortHandler.isInBoundsY(rect.origin.y) + || !viewPortHandler.isInBoundsLeft(x) + { + continue + } + + let val = e.y + + if dataSet.isDrawValuesEnabled { + drawValue( + context: context, + value: formatter.stringForValue( + val, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler + ), + xPos: x, + yPos: val >= 0.0 + ? (rect.origin.y + posOffset) + : (rect.origin.y + rect.size.height + negOffset), + font: valueFont, + align: .center, + color: dataSet.valueTextColorAt(j) + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + var px = x + var py = val >= 0.0 + ? (rect.origin.y + posOffset) + : (rect.origin.y + rect.size.height + negOffset) + + px += iconsOffset.x + py += iconsOffset.y + + ChartUtils.drawImage( + context: context, + image: icon, + x: px, + y: py, + size: icon.size + ) + } + } + } else { + // if we have stacks + + var bufferIndex = 0 + + for index in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) { + guard let e = dataSet.entryForIndex(index) as? BarChartDataEntry else { continue } + + let vals = e.yValues + + let rect = buffer.rects[bufferIndex] + + let x = rect.origin.x + rect.size.width / 2.0 + + // we still draw stacked bars, but there is one non-stacked in between + if vals == nil { + if !viewPortHandler.isInBoundsRight(x) { + break + } + + if !viewPortHandler.isInBoundsY(rect.origin.y) + || !viewPortHandler.isInBoundsLeft(x) + { + continue + } + + if dataSet.isDrawValuesEnabled { + drawValue( + context: context, + value: formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler + ), + xPos: x, + yPos: rect.origin.y + + (e.y >= 0 ? posOffset : negOffset), + font: valueFont, + align: .center, + color: dataSet.valueTextColorAt(index) + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + var px = x + var py = rect.origin.y + + (e.y >= 0 ? posOffset : negOffset) + + px += iconsOffset.x + py += iconsOffset.y + + ChartUtils.drawImage( + context: context, + image: icon, + x: px, + y: py, + size: icon.size + ) + } + } else { + // draw stack values + + let vals = vals! + var transformed = [CGPoint]() + + var posY = 0.0 + var negY = -e.negativeSum + + for k in 0 ..< vals.count { + let value = vals[k] + var y: Double + + if value == 0.0, posY == 0.0 || negY == 0.0 { + // Take care of the situation of a 0.0 value, which overlaps a non-zero bar + y = value + } else if value >= 0.0 { + posY += value + y = posY + } else { + y = negY + negY -= value + } + + transformed.append(CGPoint(x: 0.0, y: CGFloat(y * phaseY))) + } + + trans.pointValuesToPixel(&transformed) + + for k in 0 ..< transformed.count { + let val = vals[k] + let drawBelow = (val == 0.0 && negY == 0.0 && posY > 0.0) || val < 0.0 + let y = transformed[k].y + (drawBelow ? negOffset : posOffset) + + if !viewPortHandler.isInBoundsRight(x) { + break + } + + if !viewPortHandler.isInBoundsY(y) || !viewPortHandler.isInBoundsLeft(x) { + continue + } + + if dataSet.isDrawValuesEnabled { + drawValue( + context: context, + value: formatter.stringForValue( + vals[k], + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler + ), + xPos: x, + yPos: y, + font: valueFont, + align: .center, + color: dataSet.valueTextColorAt(index) + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + ChartUtils.drawImage( + context: context, + image: icon, + x: x + iconsOffset.x, + y: y + iconsOffset.y, + size: icon.size + ) + } + } + } + + bufferIndex = vals == nil ? (bufferIndex + 1) : (bufferIndex + vals!.count) + } + } + } + } + } + + /// Draws a value at the specified x and y position. + @objc open func drawValue(context: CGContext, value: String, xPos: CGFloat, yPos: CGFloat, font: NSUIFont, align: NSTextAlignment, color: NSUIColor) { + ChartUtils.drawText(context: context, text: value, point: CGPoint(x: xPos, y: yPos), align: align, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color]) + } + + override open func drawExtras(context _: CGContext) {} + + override open func drawHighlighted(context: CGContext, indices: [Highlight]) { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + context.saveGState() + + var barRect = CGRect() + + for high in indices { + guard + let set = barData.getDataSetByIndex(high.dataSetIndex) as? IBarChartDataSet, + set.isHighlightEnabled + else { continue } + + if let e = set.entryForXValue(high.x, closestToY: high.y) as? BarChartDataEntry { + if !isInBoundsX(entry: e, dataSet: set) { + continue + } + + let trans = dataProvider.getTransformer(forAxis: set.axisDependency) + + context.setFillColor(set.highlightColor.cgColor) + context.setAlpha(set.highlightAlpha) + + let isStack = high.stackIndex >= 0 && e.isStacked + + let y1: Double + let y2: Double + + if isStack { + if dataProvider.isHighlightFullBarEnabled { + y1 = e.positiveSum + y2 = -e.negativeSum + } else { + let range = e.ranges?[high.stackIndex] + + y1 = range?.from ?? 0.0 + y2 = range?.to ?? 0.0 + } + } else { + y1 = e.y + y2 = 0.0 + } + + prepareBarHighlight(x: e.x, y1: y1, y2: y2, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &barRect) + + setHighlightDrawPos(highlight: high, barRect: barRect) + + context.fill(barRect) + } + } + + context.restoreGState() + } + + /// Sets the drawing position of the highlight object based on the given bar-rect. + internal func setHighlightDrawPos(highlight high: Highlight, barRect: CGRect) { + high.setDraw(x: barRect.midX, y: barRect.origin.y) + } + + /// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements. + /// This is marked internal to support HorizontalBarChartRenderer as well. + internal func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] { + guard let chart = dataProvider as? BarChartView else { return [] } + + // Unlike Bubble & Line charts, here we use the maximum entry count to account for stacked bars + let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 + + return Array(repeating: [NSUIAccessibilityElement](), + count: maxEntryCount) + } + + /// Creates an NSUIAccessibleElement representing the smallest meaningful bar of the chart + /// i.e. in case of a stacked chart, this returns each stack, not the combined bar. + /// Note that it is marked internal to support subclass modification in the HorizontalBarChart. + internal func createAccessibleElement(withIndex idx: Int, + container: BarChartView, + dataSet: IBarChartDataSet, + dataSetIndex: Int, + stackSize: Int, + modifier: (NSUIAccessibilityElement) -> Void) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + let xAxis = container.xAxis + + guard let e = dataSet.entryForIndex(idx / stackSize) as? BarChartDataEntry else { return element } + guard let dataProvider = dataProvider else { return element } + + // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. + // i.e. due to the Double conversion, if there are more than one data set that are grouped, + // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. + let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" + + var elementValueText = dataSet.valueFormatter?.stringForValue( + e.y, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler + ) ?? "\(e.y)" + + if dataSet.isStacked, let vals = e.yValues { + let labelCount = min(dataSet.colors.count, stackSize) + + let stackLabel: String? + if dataSet.stackLabels.count > 0, labelCount > 0 { + let labelIndex = idx % labelCount + stackLabel = dataSet.stackLabels.indices.contains(labelIndex) ? dataSet.stackLabels[labelIndex] : nil + } else { + stackLabel = nil + } + + // Handles empty array of yValues + let yValue = vals.isEmpty ? 0.0 : vals[idx % vals.count] + + elementValueText = dataSet.valueFormatter?.stringForValue( + yValue, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler + ) ?? "\(e.y)" + + if let stackLabel = stackLabel { + elementValueText = stackLabel + " \(elementValueText)" + } else { + elementValueText = "\(elementValueText)" + } + } + + let dataSetCount = dataProvider.barData?.dataSetCount ?? -1 + let doesContainMultipleDataSets = dataSetCount > 1 + + element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText)" + + modifier(element) + + return element + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift b/Pods/Charts/Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift new file mode 100644 index 0000000..b12e6b5 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift @@ -0,0 +1,117 @@ +// +// BarLineScatterCandleBubbleRenderer.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 CoreGraphics +import Foundation + +@objc(BarLineScatterCandleBubbleChartRenderer) +open class BarLineScatterCandleBubbleRenderer: DataRenderer { + internal var _xBounds = XBounds() // Reusable XBounds object + + override public init(animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + } + + /// Checks if the provided entry object is in bounds for drawing considering the current animation phase. + internal func isInBoundsX(entry e: ChartDataEntry, dataSet: IBarLineScatterCandleBubbleChartDataSet) -> Bool { + let entryIndex = dataSet.entryIndex(entry: e) + return Double(entryIndex) < Double(dataSet.entryCount) * animator.phaseX + } + + /// Calculates and returns the x-bounds for the given DataSet in terms of index in their values array. + /// This includes minimum and maximum visible x, as well as range. + internal func xBounds(chart: BarLineScatterCandleBubbleChartDataProvider, + dataSet: IBarLineScatterCandleBubbleChartDataSet, + animator: Animator?) -> XBounds + { + XBounds(chart: chart, dataSet: dataSet, animator: animator) + } + + /// - Returns: `true` if the DataSet values should be drawn, `false` if not. + internal func shouldDrawValues(forDataSet set: IChartDataSet) -> Bool { + set.isVisible && (set.isDrawValuesEnabled || set.isDrawIconsEnabled) + } + + /// Class representing the bounds of the current viewport in terms of indices in the values array of a DataSet. + open class XBounds { + /// minimum visible entry index + open var min: Int = 0 + + /// maximum visible entry index + open var max: Int = 0 + + /// range of visible entry indices + open var range: Int = 0 + + public init() {} + + public init(chart: BarLineScatterCandleBubbleChartDataProvider, + dataSet: IBarLineScatterCandleBubbleChartDataSet, + animator: Animator?) + { + set(chart: chart, dataSet: dataSet, animator: animator) + } + + /// Calculates the minimum and maximum x values as well as the range between them. + open func set(chart: BarLineScatterCandleBubbleChartDataProvider, + dataSet: IBarLineScatterCandleBubbleChartDataSet, + animator: Animator?) + { + let phaseX = Swift.max(0.0, Swift.min(1.0, animator?.phaseX ?? 1.0)) + + let low = chart.lowestVisibleX + let high = chart.highestVisibleX + + let entryFrom = dataSet.entryForXValue(low, closestToY: .nan, rounding: .down) + let entryTo = dataSet.entryForXValue(high, closestToY: .nan, rounding: .up) + + min = entryFrom == nil ? 0 : dataSet.entryIndex(entry: entryFrom!) + max = entryTo == nil ? 0 : dataSet.entryIndex(entry: entryTo!) + range = Int(Double(max - min) * phaseX) + } + } +} + +extension BarLineScatterCandleBubbleRenderer.XBounds: RangeExpression { + public func relative(to _: C) -> Swift.Range + where C: Collection, Bound == C.Index + { + Swift.Range(min ... min + range) + } + + public func contains(_ element: Int) -> Bool { + (min ... min + range).contains(element) + } +} + +extension BarLineScatterCandleBubbleRenderer.XBounds: Sequence { + public struct Iterator: IteratorProtocol { + private var iterator: IndexingIterator> + + fileprivate init(min: Int, max: Int) { + iterator = (min ... max).makeIterator() + } + + public mutating func next() -> Int? { + iterator.next() + } + } + + public func makeIterator() -> Iterator { + Iterator(min: min, max: min + range) + } +} + +extension BarLineScatterCandleBubbleRenderer.XBounds: CustomDebugStringConvertible { + public var debugDescription: String { + "min:\(min), max:\(max), range:\(range)" + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/BubbleChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/BubbleChartRenderer.swift new file mode 100644 index 0000000..4eb345c --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/BubbleChartRenderer.swift @@ -0,0 +1,345 @@ +// +// BubbleChartRenderer.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import CoreGraphics +import Foundation + +open class BubbleChartRenderer: BarLineScatterCandleBubbleRenderer { + /// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver. + private lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements() + + @objc open weak var dataProvider: BubbleChartDataProvider? + + @objc public init(dataProvider: BubbleChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + override open func drawData(context: CGContext) { + guard + let dataProvider = dataProvider, + let bubbleData = dataProvider.bubbleData + else { return } + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() + + // Make the chart header the first element in the accessible elements array + if let chart = dataProvider as? BubbleChartView { + let element = createAccessibleHeader(usingChart: chart, + andData: bubbleData, + withDefaultDescription: "Bubble Chart") + accessibleChartElements.append(element) + } + + for (i, set) in (bubbleData.dataSets as! [IBubbleChartDataSet]).enumerated() where set.isVisible { + drawDataSet(context: context, dataSet: set, dataSetIndex: i) + } + + // Merge nested ordered arrays into the single accessibleChartElements. + accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 }) + accessibilityPostLayoutChangedNotification() + } + + private func getShapeSize( + entrySize: CGFloat, + maxSize: CGFloat, + reference: CGFloat, + normalizeSize: Bool + ) -> CGFloat { + let factor: CGFloat = normalizeSize + ? ((maxSize == 0.0) ? 1.0 : sqrt(entrySize / maxSize)) + : entrySize + let shapeSize: CGFloat = reference * factor + return shapeSize + } + + private var _pointBuffer = CGPoint() + private var _sizeBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawDataSet(context: CGContext, dataSet: IBubbleChartDataSet, dataSetIndex: Int) { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + let valueToPixelMatrix = trans.valueToPixelMatrix + + _sizeBuffer[0].x = 0.0 + _sizeBuffer[0].y = 0.0 + _sizeBuffer[1].x = 1.0 + _sizeBuffer[1].y = 0.0 + + trans.pointValuesToPixel(&_sizeBuffer) + + context.saveGState() + defer { context.restoreGState() } + + let normalizeSize = dataSet.isNormalizeSizeEnabled + + // calcualte the full width of 1 step on the x-axis + let maxBubbleWidth: CGFloat = abs(_sizeBuffer[1].x - _sizeBuffer[0].x) + let maxBubbleHeight: CGFloat = abs(viewPortHandler.contentBottom - viewPortHandler.contentTop) + let referenceSize: CGFloat = min(maxBubbleHeight, maxBubbleWidth) + + for j in _xBounds { + guard let entry = dataSet.entryForIndex(j) as? BubbleChartDataEntry else { continue } + + _pointBuffer.x = CGFloat(entry.x) + _pointBuffer.y = CGFloat(entry.y * phaseY) + _pointBuffer = _pointBuffer.applying(valueToPixelMatrix) + + let shapeSize = getShapeSize(entrySize: entry.size, maxSize: dataSet.maxSize, reference: referenceSize, normalizeSize: normalizeSize) + let shapeHalf = shapeSize / 2.0 + + guard + viewPortHandler.isInBoundsTop(_pointBuffer.y + shapeHalf), + viewPortHandler.isInBoundsBottom(_pointBuffer.y - shapeHalf), + viewPortHandler.isInBoundsLeft(_pointBuffer.x + shapeHalf) + else { continue } + + guard viewPortHandler.isInBoundsRight(_pointBuffer.x - shapeHalf) else { break } + + let color = dataSet.color(atIndex: j) + + let rect = CGRect( + x: _pointBuffer.x - shapeHalf, + y: _pointBuffer.y - shapeHalf, + width: shapeSize, + height: shapeSize + ) + + context.setFillColor(color.cgColor) + context.fillEllipse(in: rect) + + // Create and append the corresponding accessibility element to accessibilityOrderedElements + if let chart = dataProvider as? BubbleChartView { + let element = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet, + dataSetIndex: dataSetIndex, + shapeSize: shapeSize) + { element in + element.accessibilityFrame = rect + } + + accessibilityOrderedElements[dataSetIndex].append(element) + } + } + } + + override open func drawValues(context: CGContext) { + guard let + dataProvider = dataProvider, + let bubbleData = dataProvider.bubbleData, + isDrawingValuesAllowed(dataProvider: dataProvider), + let dataSets = bubbleData.dataSets as? [IBubbleChartDataSet] + else { return } + + let phaseX = max(0.0, min(1.0, animator.phaseX)) + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0 ..< dataSets.count { + let dataSet = dataSets[i] + + guard + shouldDrawValues(forDataSet: dataSet), + let formatter = dataSet.valueFormatter + else { continue } + + let alpha = phaseX == 1 ? phaseY : phaseX + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + let iconsOffset = dataSet.iconsOffset + + for j in _xBounds { + guard let e = dataSet.entryForIndex(j) as? BubbleChartDataEntry else { break } + + let valueTextColor = dataSet.valueTextColorAt(j).withAlphaComponent(CGFloat(alpha)) + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.y * phaseY) + pt = pt.applying(valueToPixelMatrix) + + guard viewPortHandler.isInBoundsRight(pt.x) else { break } + + guard + viewPortHandler.isInBoundsLeft(pt.x), + viewPortHandler.isInBoundsY(pt.y) + else { continue } + + let text = formatter.stringForValue( + Double(e.size), + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler + ) + + // Larger font for larger bubbles? + let valueFont = dataSet.valueFont + let lineHeight = valueFont.lineHeight + + if dataSet.isDrawValuesEnabled { + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint( + x: pt.x, + y: pt.y - (0.5 * lineHeight) + ), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + ChartUtils.drawImage(context: context, + image: icon, + x: pt.x + iconsOffset.x, + y: pt.y + iconsOffset.y, + size: icon.size) + } + } + } + } + + override open func drawExtras(context _: CGContext) {} + + override open func drawHighlighted(context: CGContext, indices: [Highlight]) { + guard + let dataProvider = dataProvider, + let bubbleData = dataProvider.bubbleData + else { return } + + context.saveGState() + defer { context.restoreGState() } + + let phaseY = animator.phaseY + + for high in indices { + guard + let dataSet = bubbleData.getDataSetByIndex(high.dataSetIndex) as? IBubbleChartDataSet, + dataSet.isHighlightEnabled, + let entry = dataSet.entryForXValue(high.x, closestToY: high.y) as? BubbleChartDataEntry, + isInBoundsX(entry: entry, dataSet: dataSet) + else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + _sizeBuffer[0].x = 0.0 + _sizeBuffer[0].y = 0.0 + _sizeBuffer[1].x = 1.0 + _sizeBuffer[1].y = 0.0 + + trans.pointValuesToPixel(&_sizeBuffer) + + let normalizeSize = dataSet.isNormalizeSizeEnabled + + // calcualte the full width of 1 step on the x-axis + let maxBubbleWidth: CGFloat = abs(_sizeBuffer[1].x - _sizeBuffer[0].x) + let maxBubbleHeight: CGFloat = abs(viewPortHandler.contentBottom - viewPortHandler.contentTop) + let referenceSize: CGFloat = min(maxBubbleHeight, maxBubbleWidth) + + _pointBuffer.x = CGFloat(entry.x) + _pointBuffer.y = CGFloat(entry.y * phaseY) + trans.pointValueToPixel(&_pointBuffer) + + let shapeSize = getShapeSize(entrySize: entry.size, maxSize: dataSet.maxSize, reference: referenceSize, normalizeSize: normalizeSize) + let shapeHalf = shapeSize / 2.0 + + guard + viewPortHandler.isInBoundsTop(_pointBuffer.y + shapeHalf), + viewPortHandler.isInBoundsBottom(_pointBuffer.y - shapeHalf), + viewPortHandler.isInBoundsLeft(_pointBuffer.x + shapeHalf) + else { continue } + + guard viewPortHandler.isInBoundsRight(_pointBuffer.x - shapeHalf) else { break } + + let originalColor = dataSet.color(atIndex: Int(entry.x)) + + var h: CGFloat = 0.0 + var s: CGFloat = 0.0 + var b: CGFloat = 0.0 + var a: CGFloat = 0.0 + + originalColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a) + + let color = NSUIColor(hue: h, saturation: s, brightness: b * 0.5, alpha: a) + let rect = CGRect( + x: _pointBuffer.x - shapeHalf, + y: _pointBuffer.y - shapeHalf, + width: shapeSize, + height: shapeSize + ) + + context.setLineWidth(dataSet.highlightCircleWidth) + context.setStrokeColor(color.cgColor) + context.strokeEllipse(in: rect) + + high.setDraw(x: _pointBuffer.x, y: _pointBuffer.y) + } + } + + /// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements. + private func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] { + guard let chart = dataProvider as? BubbleChartView else { return [] } + + let dataSetCount = chart.bubbleData?.dataSetCount ?? 0 + + return Array(repeating: [NSUIAccessibilityElement](), + count: dataSetCount) + } + + /// Creates an NSUIAccessibleElement representing individual bubbles location and relative size. + private func createAccessibleElement(withIndex idx: Int, + container: BubbleChartView, + dataSet: IBubbleChartDataSet, + dataSetIndex: Int, + shapeSize: CGFloat, + modifier: (NSUIAccessibilityElement) -> Void) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + let xAxis = container.xAxis + + guard let e = dataSet.entryForIndex(idx) else { return element } + guard let dataProvider = dataProvider else { return element } + + // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. + // i.e. due to the Double conversion, if there are more than one data set that are grouped, + // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. + let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" + + let elementValueText = dataSet.valueFormatter?.stringForValue(e.y, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) ?? "\(e.y)" + + let dataSetCount = dataProvider.bubbleData?.dataSetCount ?? -1 + let doesContainMultipleDataSets = dataSetCount > 1 + + element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText), bubble size: \(String(format: "%.2f", (shapeSize / dataSet.maxSize) * 100)) %" + + modifier(element) + + return element + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/CandleStickChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/CandleStickChartRenderer.swift new file mode 100644 index 0000000..2d81210 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/CandleStickChartRenderer.swift @@ -0,0 +1,369 @@ +// +// CandleStickChartRenderer.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 CoreGraphics +import Foundation + +open class CandleStickChartRenderer: LineScatterCandleRadarRenderer { + @objc open weak var dataProvider: CandleChartDataProvider? + + @objc public init(dataProvider: CandleChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + override open func drawData(context: CGContext) { + guard let dataProvider = dataProvider, let candleData = dataProvider.candleData else { return } + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + // Make the chart header the first element in the accessible elements array + if let chart = dataProvider as? CandleStickChartView { + let element = createAccessibleHeader(usingChart: chart, + andData: candleData, + withDefaultDescription: "CandleStick Chart") + accessibleChartElements.append(element) + } + + for set in candleData.dataSets as! [ICandleChartDataSet] where set.isVisible { + drawDataSet(context: context, dataSet: set) + } + } + + private var _shadowPoints = [CGPoint](repeating: CGPoint(), count: 4) + private var _rangePoints = [CGPoint](repeating: CGPoint(), count: 2) + private var _openPoints = [CGPoint](repeating: CGPoint(), count: 2) + private var _closePoints = [CGPoint](repeating: CGPoint(), count: 2) + private var _bodyRect = CGRect() + private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawDataSet(context: CGContext, dataSet: ICandleChartDataSet) { + guard + let dataProvider = dataProvider + else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + let barSpace = dataSet.barSpace + let showCandleBar = dataSet.showCandleBar + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + context.saveGState() + + context.setLineWidth(dataSet.shadowWidth) + + for j in _xBounds { + // get the entry + guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { continue } + + let xPos = e.x + + let open = e.open + let close = e.close + let high = e.high + let low = e.low + + let doesContainMultipleDataSets = (dataProvider.candleData?.dataSets.count ?? 1) > 1 + var accessibilityMovementDescription = "neutral" + var accessibilityRect = CGRect(x: CGFloat(xPos) + 0.5 - barSpace, + y: CGFloat(low * phaseY), + width: (2 * barSpace) - 1.0, + height: CGFloat(abs(high - low) * phaseY)) + trans.rectValueToPixel(&accessibilityRect) + + if showCandleBar { + // calculate the shadow + + _shadowPoints[0].x = CGFloat(xPos) + _shadowPoints[1].x = CGFloat(xPos) + _shadowPoints[2].x = CGFloat(xPos) + _shadowPoints[3].x = CGFloat(xPos) + + if open > close { + _shadowPoints[0].y = CGFloat(high * phaseY) + _shadowPoints[1].y = CGFloat(open * phaseY) + _shadowPoints[2].y = CGFloat(low * phaseY) + _shadowPoints[3].y = CGFloat(close * phaseY) + } else if open < close { + _shadowPoints[0].y = CGFloat(high * phaseY) + _shadowPoints[1].y = CGFloat(close * phaseY) + _shadowPoints[2].y = CGFloat(low * phaseY) + _shadowPoints[3].y = CGFloat(open * phaseY) + } else { + _shadowPoints[0].y = CGFloat(high * phaseY) + _shadowPoints[1].y = CGFloat(open * phaseY) + _shadowPoints[2].y = CGFloat(low * phaseY) + _shadowPoints[3].y = _shadowPoints[1].y + } + + trans.pointValuesToPixel(&_shadowPoints) + + // draw the shadows + + var shadowColor: NSUIColor! + if dataSet.shadowColorSameAsCandle { + if open > close { + shadowColor = dataSet.decreasingColor ?? dataSet.color(atIndex: j) + } else if open < close { + shadowColor = dataSet.increasingColor ?? dataSet.color(atIndex: j) + } else { + shadowColor = dataSet.neutralColor ?? dataSet.color(atIndex: j) + } + } + + if shadowColor === nil { + shadowColor = dataSet.shadowColor ?? dataSet.color(atIndex: j) + } + + context.setStrokeColor(shadowColor.cgColor) + context.strokeLineSegments(between: _shadowPoints) + + // calculate the body + + _bodyRect.origin.x = CGFloat(xPos) - 0.5 + barSpace + _bodyRect.origin.y = CGFloat(close * phaseY) + _bodyRect.size.width = (CGFloat(xPos) + 0.5 - barSpace) - _bodyRect.origin.x + _bodyRect.size.height = CGFloat(open * phaseY) - _bodyRect.origin.y + + trans.rectValueToPixel(&_bodyRect) + + // draw body differently for increasing and decreasing entry + + if open > close { + accessibilityMovementDescription = "decreasing" + + let color = dataSet.decreasingColor ?? dataSet.color(atIndex: j) + + if dataSet.isDecreasingFilled { + context.setFillColor(color.cgColor) + context.fill(_bodyRect) + } else { + context.setStrokeColor(color.cgColor) + context.stroke(_bodyRect) + } + } else if open < close { + accessibilityMovementDescription = "increasing" + + let color = dataSet.increasingColor ?? dataSet.color(atIndex: j) + + if dataSet.isIncreasingFilled { + context.setFillColor(color.cgColor) + context.fill(_bodyRect) + } else { + context.setStrokeColor(color.cgColor) + context.stroke(_bodyRect) + } + } else { + let color = dataSet.neutralColor ?? dataSet.color(atIndex: j) + + context.setStrokeColor(color.cgColor) + context.stroke(_bodyRect) + } + } else { + _rangePoints[0].x = CGFloat(xPos) + _rangePoints[0].y = CGFloat(high * phaseY) + _rangePoints[1].x = CGFloat(xPos) + _rangePoints[1].y = CGFloat(low * phaseY) + + _openPoints[0].x = CGFloat(xPos) - 0.5 + barSpace + _openPoints[0].y = CGFloat(open * phaseY) + _openPoints[1].x = CGFloat(xPos) + _openPoints[1].y = CGFloat(open * phaseY) + + _closePoints[0].x = CGFloat(xPos) + 0.5 - barSpace + _closePoints[0].y = CGFloat(close * phaseY) + _closePoints[1].x = CGFloat(xPos) + _closePoints[1].y = CGFloat(close * phaseY) + + trans.pointValuesToPixel(&_rangePoints) + trans.pointValuesToPixel(&_openPoints) + trans.pointValuesToPixel(&_closePoints) + + // draw the ranges + var barColor: NSUIColor! + + if open > close { + accessibilityMovementDescription = "decreasing" + barColor = dataSet.decreasingColor ?? dataSet.color(atIndex: j) + } else if open < close { + accessibilityMovementDescription = "increasing" + barColor = dataSet.increasingColor ?? dataSet.color(atIndex: j) + } else { + barColor = dataSet.neutralColor ?? dataSet.color(atIndex: j) + } + + context.setStrokeColor(barColor.cgColor) + context.strokeLineSegments(between: _rangePoints) + context.strokeLineSegments(between: _openPoints) + context.strokeLineSegments(between: _closePoints) + } + + let axElement = createAccessibleElement(withIndex: j, + container: dataProvider, + dataSet: dataSet) + { element in + element.accessibilityLabel = "\(doesContainMultipleDataSets ? "\(dataSet.label ?? "Dataset")" : "") " + "\(xPos) - \(accessibilityMovementDescription). low: \(low), high: \(high), opening: \(open), closing: \(close)" + element.accessibilityFrame = accessibilityRect + } + + accessibleChartElements.append(axElement) + } + + // Post this notification to let VoiceOver account for the redrawn frames + accessibilityPostLayoutChangedNotification() + + context.restoreGState() + } + + override open func drawValues(context: CGContext) { + guard + let dataProvider = dataProvider, + let candleData = dataProvider.candleData + else { return } + + // if values are drawn + if isDrawingValuesAllowed(dataProvider: dataProvider) { + let dataSets = candleData.dataSets + + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0 ..< dataSets.count { + guard let + dataSet = dataSets[i] as? IBarLineScatterCandleBubbleChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let valueFont = dataSet.valueFont + + guard let formatter = dataSet.valueFormatter else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + let iconsOffset = dataSet.iconsOffset + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + let lineHeight = valueFont.lineHeight + let yOffset: CGFloat = lineHeight + 5.0 + + for j in _xBounds { + guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { break } + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.high * phaseY) + pt = pt.applying(valueToPixelMatrix) + + if !viewPortHandler.isInBoundsRight(pt.x) { + break + } + + if !viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y) { + continue + } + + if dataSet.isDrawValuesEnabled { + ChartUtils.drawText( + context: context, + text: formatter.stringForValue( + e.high, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler + ), + point: CGPoint( + x: pt.x, + y: pt.y - yOffset + ), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)] + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + ChartUtils.drawImage(context: context, + image: icon, + x: pt.x + iconsOffset.x, + y: pt.y + iconsOffset.y, + size: icon.size) + } + } + } + } + } + + override open func drawExtras(context _: CGContext) {} + + override open func drawHighlighted(context: CGContext, indices: [Highlight]) { + guard + let dataProvider = dataProvider, + let candleData = dataProvider.candleData + else { return } + + context.saveGState() + + for high in indices { + guard + let set = candleData.getDataSetByIndex(high.dataSetIndex) as? ICandleChartDataSet, + set.isHighlightEnabled + else { continue } + + guard let e = set.entryForXValue(high.x, closestToY: high.y) as? CandleChartDataEntry else { continue } + + if !isInBoundsX(entry: e, dataSet: set) { + continue + } + + let trans = dataProvider.getTransformer(forAxis: set.axisDependency) + + context.setStrokeColor(set.highlightColor.cgColor) + context.setLineWidth(set.highlightLineWidth) + + if set.highlightLineDashLengths != nil { + context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + let lowValue = e.low * Double(animator.phaseY) + let highValue = e.high * Double(animator.phaseY) + let y = (lowValue + highValue) / 2.0 + + let pt = trans.pixelForValues(x: e.x, y: y) + + high.setDraw(pt: pt) + + // draw the lines + drawHighlightLines(context: context, point: pt, set: set) + } + + context.restoreGState() + } + + private func createAccessibleElement(withIndex _: Int, + container: CandleChartDataProvider, + dataSet _: ICandleChartDataSet, + modifier: (NSUIAccessibilityElement) -> Void) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + + // The modifier allows changing of traits and frame depending on highlight, rotation, etc + modifier(element) + + return element + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/ChartDataRendererBase.swift b/Pods/Charts/Source/Charts/Renderers/ChartDataRendererBase.swift new file mode 100644 index 0000000..cdb260f --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/ChartDataRendererBase.swift @@ -0,0 +1,95 @@ +// +// DataRenderer.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +@objc(ChartDataRendererBase) +open class DataRenderer: Renderer { + /// An array of accessibility elements that are presented to the ChartViewBase accessibility methods. + /// + /// Note that the order of elements in this array determines the order in which they are presented and navigated by + /// Accessibility clients such as VoiceOver. + /// + /// Renderers should ensure that the order of elements makes sense to a client presenting an audio-only interface to a user. + /// Subclasses should populate this array in drawData() or drawDataSet() to make the chart accessible. + @objc final var accessibleChartElements: [NSUIAccessibilityElement] = [] + + @objc public let animator: Animator + + @objc public init(animator: Animator, viewPortHandler: ViewPortHandler) { + self.animator = animator + + super.init(viewPortHandler: viewPortHandler) + } + + @objc open func drawData(context _: CGContext) { + fatalError("drawData() cannot be called on DataRenderer") + } + + @objc open func drawValues(context _: CGContext) { + fatalError("drawValues() cannot be called on DataRenderer") + } + + @objc open func drawExtras(context _: CGContext) { + fatalError("drawExtras() cannot be called on DataRenderer") + } + + /// Draws all highlight indicators for the values that are currently highlighted. + /// + /// - Parameters: + /// - indices: the highlighted values + @objc open func drawHighlighted(context _: CGContext, indices _: [Highlight]) { + fatalError("drawHighlighted() cannot be called on DataRenderer") + } + + /// An opportunity for initializing internal buffers used for rendering with a new size. + /// Since this might do memory allocations, it should only be called if necessary. + @objc open func initBuffers() {} + + @objc open func isDrawingValuesAllowed(dataProvider: ChartDataProvider?) -> Bool { + guard let data = dataProvider?.data else { return false } + return data.entryCount < Int(CGFloat(dataProvider?.maxVisibleCount ?? 0) * viewPortHandler.scaleX) + } + + /// Creates an ```NSUIAccessibilityElement``` that acts as the first and primary header describing a chart view. + /// + /// - Parameters: + /// - chart: The chartView object being described + /// - data: A non optional data source about the chart + /// - defaultDescription: A simple string describing the type/design of Chart. + /// - Returns: A header ```NSUIAccessibilityElement``` that can be added to accessibleChartElements. + @objc internal func createAccessibleHeader(usingChart chart: ChartViewBase, + andData data: ChartData, + withDefaultDescription defaultDescription: String = "Chart") -> NSUIAccessibilityElement + { + let chartDescriptionText = chart.chartDescription?.text ?? defaultDescription + let dataSetDescriptions = data.dataSets.map { $0.label ?? "" } + let dataSetDescriptionText = dataSetDescriptions.joined(separator: ", ") + let dataSetCount = data.dataSets.count + + let + element = NSUIAccessibilityElement(accessibilityContainer: chart) + element.accessibilityLabel = chartDescriptionText + ". \(dataSetCount) dataset\(dataSetCount == 1 ? "" : "s"). \(dataSetDescriptionText)" + element.accessibilityFrame = chart.bounds + element.isHeader = true + + return element + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/CombinedChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/CombinedChartRenderer.swift new file mode 100644 index 0000000..378d1bb --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/CombinedChartRenderer.swift @@ -0,0 +1,170 @@ +// +// CombinedChartRenderer.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 CoreGraphics +import Foundation + +open class CombinedChartRenderer: DataRenderer { + @objc open weak var chart: CombinedChartView? + + /// if set to true, all values are drawn above their bars, instead of below their top + @objc open var drawValueAboveBarEnabled = true + + /// if set to true, a grey area is drawn behind each bar that indicates the maximum value + @objc open var drawBarShadowEnabled = false + + internal var _renderers = [DataRenderer]() + + internal var _drawOrder: [CombinedChartView.DrawOrder] = [.bar, .bubble, .line, .candle, .scatter] + + @objc public init(chart: CombinedChartView, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.chart = chart + + createRenderers() + } + + /// Creates the renderers needed for this combined-renderer in the required order. Also takes the DrawOrder into consideration. + internal func createRenderers() { + _renderers = [DataRenderer]() + + guard let chart = chart else { return } + + for order in drawOrder { + switch order { + case .bar: + if chart.barData !== nil { + _renderers.append(BarChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + + case .line: + if chart.lineData !== nil { + _renderers.append(LineChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + + case .candle: + if chart.candleData !== nil { + _renderers.append(CandleStickChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + + case .scatter: + if chart.scatterData !== nil { + _renderers.append(ScatterChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + + case .bubble: + if chart.bubbleData !== nil { + _renderers.append(BubbleChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + } + } + } + + override open func initBuffers() { + _renderers.forEach { $0.initBuffers() } + } + + override open func drawData(context: CGContext) { + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + if + let combinedChart = chart, + let data = combinedChart.data + { + // Make the chart header the first element in the accessible elements array + let element = createAccessibleHeader(usingChart: combinedChart, + andData: data, + withDefaultDescription: "Combined Chart") + accessibleChartElements.append(element) + } + + // TODO: Due to the potential complexity of data presented in Combined charts, a more usable way + // for VO accessibility would be to use axis based traversal rather than by dataset. + // Hence, accessibleChartElements is not populated below. (Individual renderers guard against dataSource being their respective views) + _renderers.forEach { $0.drawData(context: context) } + } + + override open func drawValues(context: CGContext) { + _renderers.forEach { $0.drawValues(context: context) } + } + + override open func drawExtras(context: CGContext) { + _renderers.forEach { $0.drawExtras(context: context) } + } + + override open func drawHighlighted(context: CGContext, indices: [Highlight]) { + for renderer in _renderers { + var data: ChartData? + + if renderer is BarChartRenderer { + data = (renderer as! BarChartRenderer).dataProvider?.barData + } else if renderer is LineChartRenderer { + data = (renderer as! LineChartRenderer).dataProvider?.lineData + } else if renderer is CandleStickChartRenderer { + data = (renderer as! CandleStickChartRenderer).dataProvider?.candleData + } else if renderer is ScatterChartRenderer { + data = (renderer as! ScatterChartRenderer).dataProvider?.scatterData + } else if renderer is BubbleChartRenderer { + data = (renderer as! BubbleChartRenderer).dataProvider?.bubbleData + } + + let dataIndex: Int? = { + guard let data = data else { return nil } + return (chart?.data as? CombinedChartData)? + .allData + .firstIndex(of: data) + }() + + let dataIndices = indices.filter { $0.dataIndex == dataIndex || $0.dataIndex == -1 } + + renderer.drawHighlighted(context: context, indices: dataIndices) + } + } + + /// - Returns: The sub-renderer object at the specified index. + @objc open func getSubRenderer(index: Int) -> DataRenderer? { + if index >= _renderers.count || index < 0 { + return nil + } else { + return _renderers[index] + } + } + + /// All sub-renderers. + @objc open var subRenderers: [DataRenderer] { + get { _renderers } + set { _renderers = newValue } + } + + // MARK: Accessors + + /// `true` if drawing values above bars is enabled, `false` ifnot + @objc open var isDrawValueAboveBarEnabled: Bool { drawValueAboveBarEnabled } + + /// `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot + @objc open var isDrawBarShadowEnabled: Bool { 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. + open var drawOrder: [CombinedChartView.DrawOrder] { + get { + _drawOrder + } + set { + if newValue.count > 0 { + _drawOrder = newValue + } + } + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/HorizontalBarChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/HorizontalBarChartRenderer.swift new file mode 100644 index 0000000..a943ed2 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/HorizontalBarChartRenderer.swift @@ -0,0 +1,564 @@ +// +// HorizontalBarChartRenderer.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +open class HorizontalBarChartRenderer: BarChartRenderer { + private class Buffer { + var rects = [CGRect]() + } + + override public init(dataProvider: BarChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(dataProvider: dataProvider, animator: animator, viewPortHandler: viewPortHandler) + } + + // [CGRect] per dataset + private var _buffers = [Buffer]() + + override open func initBuffers() { + if let barData = dataProvider?.barData { + // Matche buffers count to dataset count + if _buffers.count != barData.dataSetCount { + while _buffers.count < barData.dataSetCount { + _buffers.append(Buffer()) + } + while _buffers.count > barData.dataSetCount { + _buffers.removeLast() + } + } + + for i in stride(from: 0, to: barData.dataSetCount, by: 1) { + let set = barData.dataSets[i] as! IBarChartDataSet + let size = set.entryCount * (set.isStacked ? set.stackSize : 1) + if _buffers[i].rects.count != size { + _buffers[i].rects = [CGRect](repeating: CGRect(), count: size) + } + } + } else { + _buffers.removeAll() + } + } + + private func prepareBuffer(dataSet: IBarChartDataSet, index: Int) { + guard let + dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + let barWidthHalf = barData.barWidth / 2.0 + + let buffer = _buffers[index] + var bufferIndex = 0 + let containsStacks = dataSet.isStacked + + let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) + let phaseY = animator.phaseY + var barRect = CGRect() + var x: Double + var y: Double + + for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) { + guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } + + let vals = e.yValues + + x = e.x + y = e.y + + if !containsStacks || vals == nil { + let bottom = CGFloat(x - barWidthHalf) + let top = CGFloat(x + barWidthHalf) + var right = isInverted + ? (y <= 0.0 ? CGFloat(y) : 0) + : (y >= 0.0 ? CGFloat(y) : 0) + var left = isInverted + ? (y >= 0.0 ? CGFloat(y) : 0) + : (y <= 0.0 ? CGFloat(y) : 0) + + // multiply the height of the rect with the phase + if right > 0 { + right *= CGFloat(phaseY) + } else { + left *= CGFloat(phaseY) + } + + barRect.origin.x = left + barRect.size.width = right - left + barRect.origin.y = top + barRect.size.height = bottom - top + + buffer.rects[bufferIndex] = barRect + bufferIndex += 1 + } else { + var posY = 0.0 + var negY = -e.negativeSum + var yStart = 0.0 + + // fill the stack + for k in 0 ..< vals!.count { + let value = vals![k] + + if value == 0.0, posY == 0.0 || negY == 0.0 { + // Take care of the situation of a 0.0 value, which overlaps a non-zero bar + y = value + yStart = y + } else if value >= 0.0 { + y = posY + yStart = posY + value + posY = yStart + } else { + y = negY + yStart = negY + abs(value) + negY += abs(value) + } + + let bottom = CGFloat(x - barWidthHalf) + let top = CGFloat(x + barWidthHalf) + var right = isInverted + ? (y <= yStart ? CGFloat(y) : CGFloat(yStart)) + : (y >= yStart ? CGFloat(y) : CGFloat(yStart)) + var left = isInverted + ? (y >= yStart ? CGFloat(y) : CGFloat(yStart)) + : (y <= yStart ? CGFloat(y) : CGFloat(yStart)) + + // multiply the height of the rect with the phase + right *= CGFloat(phaseY) + left *= CGFloat(phaseY) + + barRect.origin.x = left + barRect.size.width = right - left + barRect.origin.y = top + barRect.size.height = bottom - top + + buffer.rects[bufferIndex] = barRect + bufferIndex += 1 + } + } + } + } + + private var _barShadowRectBuffer = CGRect() + + override open func drawDataSet(context: CGContext, dataSet: IBarChartDataSet, index: Int) { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + prepareBuffer(dataSet: dataSet, index: index) + trans.rectValuesToPixel(&_buffers[index].rects) + + let borderWidth = dataSet.barBorderWidth + let borderColor = dataSet.barBorderColor + let drawBorder = borderWidth > 0.0 + + context.saveGState() + + // draw the bar shadow before the values + if dataProvider.isDrawBarShadowEnabled { + guard let barData = dataProvider.barData else { return } + + let barWidth = barData.barWidth + let barWidthHalf = barWidth / 2.0 + var x: Double = 0.0 + + for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) { + guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } + + x = e.x + + _barShadowRectBuffer.origin.y = CGFloat(x - barWidthHalf) + _barShadowRectBuffer.size.height = CGFloat(barWidth) + + trans.rectValueToPixel(&_barShadowRectBuffer) + + if !viewPortHandler.isInBoundsTop(_barShadowRectBuffer.origin.y + _barShadowRectBuffer.size.height) { + break + } + + if !viewPortHandler.isInBoundsBottom(_barShadowRectBuffer.origin.y) { + continue + } + + _barShadowRectBuffer.origin.x = viewPortHandler.contentLeft + _barShadowRectBuffer.size.width = viewPortHandler.contentWidth + + context.setFillColor(dataSet.barShadowColor.cgColor) + context.fill(_barShadowRectBuffer) + } + } + + let buffer = _buffers[index] + + let isSingleColor = dataSet.colors.count == 1 + + if isSingleColor { + context.setFillColor(dataSet.color(atIndex: 0).cgColor) + } + + // In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements + let isStacked = dataSet.isStacked + let stackSize = isStacked ? dataSet.stackSize : 1 + + for j in stride(from: 0, to: buffer.rects.count, by: 1) { + let barRect = buffer.rects[j] + + if !viewPortHandler.isInBoundsTop(barRect.origin.y + barRect.size.height) { + break + } + + if !viewPortHandler.isInBoundsBottom(barRect.origin.y) { + continue + } + + if !isSingleColor { + // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. + context.setFillColor(dataSet.color(atIndex: j).cgColor) + } + + context.fill(barRect) + + if drawBorder { + context.setStrokeColor(borderColor.cgColor) + context.setLineWidth(borderWidth) + context.stroke(barRect) + } + + // Create and append the corresponding accessibility element to accessibilityOrderedElements (see BarChartRenderer) + if let chart = dataProvider as? BarChartView { + let element = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet, + dataSetIndex: index, + stackSize: stackSize) + { element in + element.accessibilityFrame = barRect + } + + accessibilityOrderedElements[j / stackSize].append(element) + } + } + + context.restoreGState() + } + + override open func prepareBarHighlight( + x: Double, + y1: Double, + y2: Double, + barWidthHalf: Double, + trans: Transformer, + rect: inout CGRect + ) { + let top = x - barWidthHalf + let bottom = x + barWidthHalf + let left = y1 + let right = y2 + + rect.origin.x = CGFloat(left) + rect.origin.y = CGFloat(top) + rect.size.width = CGFloat(right - left) + rect.size.height = CGFloat(bottom - top) + + trans.rectValueToPixelHorizontal(&rect, phaseY: animator.phaseY) + } + + override open func drawValues(context: CGContext) { + // if values are drawn + if isDrawingValuesAllowed(dataProvider: dataProvider) { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + let dataSets = barData.dataSets + + let textAlign = NSTextAlignment.left + + let valueOffsetPlus: CGFloat = 5.0 + var posOffset: CGFloat + var negOffset: CGFloat + let drawValueAboveBar = dataProvider.isDrawValueAboveBarEnabled + + for dataSetIndex in 0 ..< barData.dataSetCount { + guard let + dataSet = dataSets[dataSetIndex] as? IBarChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) + + let valueFont = dataSet.valueFont + let yOffset = -valueFont.lineHeight / 2.0 + + guard let formatter = dataSet.valueFormatter else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + let iconsOffset = dataSet.iconsOffset + + let buffer = _buffers[dataSetIndex] + + // if only single values are drawn (sum) + if !dataSet.isStacked { + for j in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) { + guard let e = dataSet.entryForIndex(j) as? BarChartDataEntry else { continue } + + let rect = buffer.rects[j] + + let y = rect.origin.y + rect.size.height / 2.0 + + if !viewPortHandler.isInBoundsTop(rect.origin.y) { + break + } + + if !viewPortHandler.isInBoundsX(rect.origin.x) { + continue + } + + if !viewPortHandler.isInBoundsBottom(rect.origin.y) { + continue + } + + let val = e.y + let valueText = formatter.stringForValue( + val, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler + ) + + // calculate the correct offset depending on the draw position of the value + let valueTextWidth = valueText.size(withAttributes: [NSAttributedString.Key.font: valueFont]).width + posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) + negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) - rect.size.width + + if isInverted { + posOffset = -posOffset - valueTextWidth + negOffset = -negOffset - valueTextWidth + } + + if dataSet.isDrawValuesEnabled { + drawValue( + context: context, + value: valueText, + xPos: (rect.origin.x + rect.size.width) + + (val >= 0.0 ? posOffset : negOffset), + yPos: y + yOffset, + font: valueFont, + align: textAlign, + color: dataSet.valueTextColorAt(j) + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + var px = (rect.origin.x + rect.size.width) + + (val >= 0.0 ? posOffset : negOffset) + var py = y + + px += iconsOffset.x + py += iconsOffset.y + + ChartUtils.drawImage( + context: context, + image: icon, + x: px, + y: py, + size: icon.size + ) + } + } + } else { + // if each value of a potential stack should be drawn + + var bufferIndex = 0 + + for index in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) { + guard let e = dataSet.entryForIndex(index) as? BarChartDataEntry else { continue } + + let rect = buffer.rects[bufferIndex] + + let vals = e.yValues + + // we still draw stacked bars, but there is one non-stacked in between + if vals == nil { + if !viewPortHandler.isInBoundsTop(rect.origin.y) { + break + } + + if !viewPortHandler.isInBoundsX(rect.origin.x) { + continue + } + + if !viewPortHandler.isInBoundsBottom(rect.origin.y) { + continue + } + + let val = e.y + let valueText = formatter.stringForValue( + val, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler + ) + + // calculate the correct offset depending on the draw position of the value + let valueTextWidth = valueText.size(withAttributes: [NSAttributedString.Key.font: valueFont]).width + posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) + negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) + + if isInverted { + posOffset = -posOffset - valueTextWidth + negOffset = -negOffset - valueTextWidth + } + + if dataSet.isDrawValuesEnabled { + drawValue( + context: context, + value: valueText, + xPos: (rect.origin.x + rect.size.width) + + (val >= 0.0 ? posOffset : negOffset), + yPos: rect.origin.y + yOffset, + font: valueFont, + align: textAlign, + color: dataSet.valueTextColorAt(index) + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + var px = (rect.origin.x + rect.size.width) + + (val >= 0.0 ? posOffset : negOffset) + var py = rect.origin.y + + px += iconsOffset.x + py += iconsOffset.y + + ChartUtils.drawImage( + context: context, + image: icon, + x: px, + y: py, + size: icon.size + ) + } + } else { + let vals = vals! + var transformed = [CGPoint]() + + var posY = 0.0 + var negY = -e.negativeSum + + for k in 0 ..< vals.count { + let value = vals[k] + var y: Double + + if value == 0.0, posY == 0.0 || negY == 0.0 { + // Take care of the situation of a 0.0 value, which overlaps a non-zero bar + y = value + } else if value >= 0.0 { + posY += value + y = posY + } else { + y = negY + negY -= value + } + + transformed.append(CGPoint(x: CGFloat(y * phaseY), y: 0.0)) + } + + trans.pointValuesToPixel(&transformed) + + for k in 0 ..< transformed.count { + let val = vals[k] + let valueText = formatter.stringForValue( + val, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler + ) + + // calculate the correct offset depending on the draw position of the value + let valueTextWidth = valueText.size(withAttributes: [NSAttributedString.Key.font: valueFont]).width + posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) + negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) + + if isInverted { + posOffset = -posOffset - valueTextWidth + negOffset = -negOffset - valueTextWidth + } + + let drawBelow = (val == 0.0 && negY == 0.0 && posY > 0.0) || val < 0.0 + + let x = transformed[k].x + (drawBelow ? negOffset : posOffset) + let y = rect.origin.y + rect.size.height / 2.0 + + if !viewPortHandler.isInBoundsTop(y) { + break + } + + if !viewPortHandler.isInBoundsX(x) { + continue + } + + if !viewPortHandler.isInBoundsBottom(y) { + continue + } + + if dataSet.isDrawValuesEnabled { + drawValue(context: context, + value: valueText, + xPos: x, + yPos: y + yOffset, + font: valueFont, + align: textAlign, + color: dataSet.valueTextColorAt(index)) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + ChartUtils.drawImage( + context: context, + image: icon, + x: x + iconsOffset.x, + y: y + iconsOffset.y, + size: icon.size + ) + } + } + } + + bufferIndex = vals == nil ? (bufferIndex + 1) : (bufferIndex + vals!.count) + } + } + } + } + } + + override open func isDrawingValuesAllowed(dataProvider: ChartDataProvider?) -> Bool { + guard let data = dataProvider?.data + else { return false } + return data.entryCount < Int(CGFloat(dataProvider?.maxVisibleCount ?? 0) * viewPortHandler.scaleY) + } + + /// Sets the drawing position of the highlight object based on the riven bar-rect. + override internal func setHighlightDrawPos(highlight high: Highlight, barRect: CGRect) { + high.setDraw(x: barRect.midY, y: barRect.origin.x + barRect.size.width) + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/LegendRenderer.swift b/Pods/Charts/Source/Charts/Renderers/LegendRenderer.swift new file mode 100755 index 0000000..23e0617 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/LegendRenderer.swift @@ -0,0 +1,508 @@ +// +// LegendRenderer.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 CoreGraphics +import Foundation + +@objc(ChartLegendRenderer) +open class LegendRenderer: Renderer { + /// the legend object this renderer renders + @objc open var legend: Legend? + + @objc public init(viewPortHandler: ViewPortHandler, legend: Legend?) { + super.init(viewPortHandler: viewPortHandler) + + self.legend = legend + } + + /// Prepares the legend and calculates all needed forms, labels and colors. + @objc open func computeLegend(data: ChartData) { + guard let legend = legend else { return } + + if !legend.isLegendCustom { + var entries: [LegendEntry] = [] + + // loop for building up the colors and labels used in the legend + for i in 0 ..< data.dataSetCount { + guard let dataSet = data.getDataSetByIndex(i) else { continue } + + let clrs: [NSUIColor] = dataSet.colors + let entryCount = dataSet.entryCount + + // if we have a barchart with stacked bars + if dataSet is IBarChartDataSet, + (dataSet as! IBarChartDataSet).isStacked + { + let bds = dataSet as! IBarChartDataSet + let sLabels = bds.stackLabels + let minEntries = min(clrs.count, bds.stackSize) + + for j in 0 ..< minEntries { + let label: String? + if sLabels.count > 0 { + let labelIndex = j % minEntries + label = sLabels.indices.contains(labelIndex) ? sLabels[labelIndex] : nil + } else { + label = nil + } + + entries.append( + LegendEntry( + label: label, + form: dataSet.form, + formSize: dataSet.formSize, + formLineWidth: dataSet.formLineWidth, + formLineDashPhase: dataSet.formLineDashPhase, + formLineDashLengths: dataSet.formLineDashLengths, + formColor: clrs[j] + ) + ) + } + + if dataSet.label != nil { + // add the legend description label + + entries.append( + LegendEntry( + label: dataSet.label, + form: .none, + formSize: CGFloat.nan, + formLineWidth: CGFloat.nan, + formLineDashPhase: 0.0, + formLineDashLengths: nil, + formColor: nil + ) + ) + } + } else if dataSet is IPieChartDataSet { + let pds = dataSet as! IPieChartDataSet + + for j in 0 ..< min(clrs.count, entryCount) { + entries.append( + LegendEntry( + label: (pds.entryForIndex(j) as? PieChartDataEntry)?.label, + form: dataSet.form, + formSize: dataSet.formSize, + formLineWidth: dataSet.formLineWidth, + formLineDashPhase: dataSet.formLineDashPhase, + formLineDashLengths: dataSet.formLineDashLengths, + formColor: clrs[j] + ) + ) + } + + if dataSet.label != nil { + // add the legend description label + + entries.append( + LegendEntry( + label: dataSet.label, + form: .none, + formSize: CGFloat.nan, + formLineWidth: CGFloat.nan, + formLineDashPhase: 0.0, + formLineDashLengths: nil, + formColor: nil + ) + ) + } + } else if dataSet is ICandleChartDataSet, + (dataSet as! ICandleChartDataSet).decreasingColor != nil + { + let candleDataSet = dataSet as! ICandleChartDataSet + + entries.append( + LegendEntry( + label: nil, + form: dataSet.form, + formSize: dataSet.formSize, + formLineWidth: dataSet.formLineWidth, + formLineDashPhase: dataSet.formLineDashPhase, + formLineDashLengths: dataSet.formLineDashLengths, + formColor: candleDataSet.decreasingColor + ) + ) + + entries.append( + LegendEntry( + label: dataSet.label, + form: dataSet.form, + formSize: dataSet.formSize, + formLineWidth: dataSet.formLineWidth, + formLineDashPhase: dataSet.formLineDashPhase, + formLineDashLengths: dataSet.formLineDashLengths, + formColor: candleDataSet.increasingColor + ) + ) + } else + { // all others + for j in 0 ..< min(clrs.count, entryCount) { + let label: String? + + // if multiple colors are set for a DataSet, group them + if j < clrs.count - 1, j < entryCount - 1 { + label = nil + } else + { // add label to the last entry + label = dataSet.label + } + + entries.append( + LegendEntry( + label: label, + form: dataSet.form, + formSize: dataSet.formSize, + formLineWidth: dataSet.formLineWidth, + formLineDashPhase: dataSet.formLineDashPhase, + formLineDashLengths: dataSet.formLineDashLengths, + formColor: clrs[j] + ) + ) + } + } + } + + legend.entries = entries + legend.extraEntries + } + + // calculate all dimensions of the legend + legend.calculateDimensions(labelFont: legend.font, viewPortHandler: viewPortHandler) + } + + @objc open func renderLegend(context: CGContext) { + guard let legend = legend else { return } + + if !legend.enabled { + return + } + + let labelFont = legend.font + let labelTextColor = legend.textColor + let labelLineHeight = labelFont.lineHeight + let formYOffset = labelLineHeight / 2.0 + + let entries = legend.entries + + let defaultFormSize = legend.formSize + let formToTextSpace = legend.formToTextSpace + let xEntrySpace = legend.xEntrySpace + let yEntrySpace = legend.yEntrySpace + + let orientation = legend.orientation + let horizontalAlignment = legend.horizontalAlignment + let verticalAlignment = legend.verticalAlignment + let direction = legend.direction + + // space between the entries + let stackSpace = legend.stackSpace + + let yoffset = legend.yOffset + let xoffset = legend.xOffset + var originPosX: CGFloat = 0.0 + + switch horizontalAlignment { + case .left: + + if orientation == .vertical { + originPosX = xoffset + } else { + originPosX = viewPortHandler.contentLeft + xoffset + } + + if direction == .rightToLeft { + originPosX += legend.neededWidth + } + + case .right: + + if orientation == .vertical { + originPosX = viewPortHandler.chartWidth - xoffset + } else { + originPosX = viewPortHandler.contentRight - xoffset + } + + if direction == .leftToRight { + originPosX -= legend.neededWidth + } + + case .center: + + if orientation == .vertical { + originPosX = viewPortHandler.chartWidth / 2.0 + } else { + originPosX = viewPortHandler.contentLeft + + viewPortHandler.contentWidth / 2.0 + } + + originPosX += (direction == .leftToRight + ? +xoffset + : -xoffset) + + // Horizontally layed out legends do the center offset on a line basis, + // So here we offset the vertical ones only. + if orientation == .vertical { + if direction == .leftToRight { + originPosX -= legend.neededWidth / 2.0 - xoffset + } else { + originPosX += legend.neededWidth / 2.0 - xoffset + } + } + } + + switch orientation { + case .horizontal: + + let calculatedLineSizes = legend.calculatedLineSizes + let calculatedLabelSizes = legend.calculatedLabelSizes + let calculatedLabelBreakPoints = legend.calculatedLabelBreakPoints + + var posX: CGFloat = originPosX + var posY: CGFloat + + switch verticalAlignment { + case .top: + posY = yoffset + + case .bottom: + posY = viewPortHandler.chartHeight - yoffset - legend.neededHeight + + case .center: + posY = (viewPortHandler.chartHeight - legend.neededHeight) / 2.0 + yoffset + } + + var lineIndex: Int = 0 + + for i in 0 ..< entries.count { + let e = entries[i] + let drawingForm = e.form != .none + let formSize = e.formSize.isNaN ? defaultFormSize : e.formSize + + if i < calculatedLabelBreakPoints.count, + calculatedLabelBreakPoints[i] + { + posX = originPosX + posY += labelLineHeight + yEntrySpace + } + + if posX == originPosX, + horizontalAlignment == .center, + lineIndex < calculatedLineSizes.count + { + posX += (direction == .rightToLeft + ? calculatedLineSizes[lineIndex].width + : -calculatedLineSizes[lineIndex].width) / 2.0 + lineIndex += 1 + } + + let isStacked = e.label == nil // grouped forms have null labels + + if drawingForm { + if direction == .rightToLeft { + posX -= formSize + } + + drawForm( + context: context, + x: posX, + y: posY + formYOffset, + entry: e, + legend: legend + ) + + if direction == .leftToRight { + posX += formSize + } + } + + if !isStacked { + if drawingForm { + posX += direction == .rightToLeft ? -formToTextSpace : formToTextSpace + } + + if direction == .rightToLeft { + posX -= calculatedLabelSizes[i].width + } + + drawLabel( + context: context, + x: posX, + y: posY, + label: e.label!, + font: labelFont, + textColor: labelTextColor + ) + + if direction == .leftToRight { + posX += calculatedLabelSizes[i].width + } + + posX += direction == .rightToLeft ? -xEntrySpace : xEntrySpace + } else { + posX += direction == .rightToLeft ? -stackSpace : stackSpace + } + } + + case .vertical: + + // contains the stacked legend size in pixels + var stack = CGFloat(0.0) + var wasStacked = false + + var posY: CGFloat = 0.0 + + switch verticalAlignment { + case .top: + posY = (horizontalAlignment == .center + ? 0.0 + : viewPortHandler.contentTop) + posY += yoffset + + case .bottom: + posY = (horizontalAlignment == .center + ? viewPortHandler.chartHeight + : viewPortHandler.contentBottom) + posY -= legend.neededHeight + yoffset + + case .center: + + posY = viewPortHandler.chartHeight / 2.0 - legend.neededHeight / 2.0 + legend.yOffset + } + + for i in 0 ..< entries.count { + let e = entries[i] + let drawingForm = e.form != .none + let formSize = e.formSize.isNaN ? defaultFormSize : e.formSize + + var posX = originPosX + + if drawingForm { + if direction == .leftToRight { + posX += stack + } else { + posX -= formSize - stack + } + + drawForm( + context: context, + x: posX, + y: posY + formYOffset, + entry: e, + legend: legend + ) + + if direction == .leftToRight { + posX += formSize + } + } + + if e.label != nil { + if drawingForm, !wasStacked { + posX += direction == .leftToRight ? formToTextSpace : -formToTextSpace + } else if wasStacked { + posX = originPosX + } + + if direction == .rightToLeft { + posX -= (e.label! as NSString).size(withAttributes: [.font: labelFont]).width + } + + if !wasStacked { + drawLabel(context: context, x: posX, y: posY, label: e.label!, font: labelFont, textColor: labelTextColor) + } else { + posY += labelLineHeight + yEntrySpace + drawLabel(context: context, x: posX, y: posY, label: e.label!, font: labelFont, textColor: labelTextColor) + } + + // make a step down + posY += labelLineHeight + yEntrySpace + stack = 0.0 + } else { + stack += formSize + stackSpace + wasStacked = true + } + } + } + } + + private var _formLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + /// Draws the Legend-form at the given position with the color at the given index. + @objc open func drawForm( + context: CGContext, + x: CGFloat, + y: CGFloat, + entry: LegendEntry, + legend: Legend + ) { + guard + let formColor = entry.formColor, + formColor != NSUIColor.clear + else { return } + + var form = entry.form + if form == .default { + form = legend.form + } + + let formSize = entry.formSize.isNaN ? legend.formSize : entry.formSize + + context.saveGState() + defer { context.restoreGState() } + + switch form { + case .none: + // Do nothing + break + + case .empty: + // Do not draw, but keep space for the form + break + + case .default: fallthrough + case .circle: + + context.setFillColor(formColor.cgColor) + context.fillEllipse(in: CGRect(x: x, y: y - formSize / 2.0, width: formSize, height: formSize)) + + case .square: + + context.setFillColor(formColor.cgColor) + context.fill(CGRect(x: x, y: y - formSize / 2.0, width: formSize, height: formSize)) + + case .line: + + let formLineWidth = entry.formLineWidth.isNaN ? legend.formLineWidth : entry.formLineWidth + let formLineDashPhase = entry.formLineDashPhase.isNaN ? legend.formLineDashPhase : entry.formLineDashPhase + let formLineDashLengths = entry.formLineDashLengths == nil ? legend.formLineDashLengths : entry.formLineDashLengths + + context.setLineWidth(formLineWidth) + + if formLineDashLengths != nil, formLineDashLengths!.count > 0 { + context.setLineDash(phase: formLineDashPhase, lengths: formLineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.setStrokeColor(formColor.cgColor) + + _formLineSegmentsBuffer[0].x = x + _formLineSegmentsBuffer[0].y = y + _formLineSegmentsBuffer[1].x = x + formSize + _formLineSegmentsBuffer[1].y = y + context.strokeLineSegments(between: _formLineSegmentsBuffer) + } + } + + /// Draws the provided label at the given position. + @objc open func drawLabel(context: CGContext, x: CGFloat, y: CGFloat, label: String, font: NSUIFont, textColor: NSUIColor) { + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: x, y: y), align: .left, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: textColor]) + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/LineChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/LineChartRenderer.swift new file mode 100644 index 0000000..52fc1b0 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/LineChartRenderer.swift @@ -0,0 +1,730 @@ +// +// LineChartRenderer.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 CoreGraphics +import Foundation + +open class LineChartRenderer: LineRadarRenderer { + // TODO: Currently, this nesting isn't necessary for LineCharts. However, it will make it much easier to add a custom rotor + // that navigates between datasets. + // NOTE: Unlike the other renderers, LineChartRenderer populates accessibleChartElements in drawCircles due to the nature of its drawing options. + /// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver. + private lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements() + + @objc open weak var dataProvider: LineChartDataProvider? + + @objc public init(dataProvider: LineChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + override open func drawData(context: CGContext) { + guard let lineData = dataProvider?.lineData else { return } + + for i in 0 ..< lineData.dataSetCount { + guard let set = lineData.getDataSetByIndex(i) else { continue } + + if set.isVisible { + if !(set is ILineChartDataSet) { + fatalError("Datasets for LineChartRenderer must conform to ILineChartDataSet") + } + + drawDataSet(context: context, dataSet: set as! ILineChartDataSet) + } + } + } + + @objc open func drawDataSet(context: CGContext, dataSet: ILineChartDataSet) { + if dataSet.entryCount < 1 { + return + } + + context.saveGState() + + context.setLineWidth(dataSet.lineWidth) + if dataSet.lineDashLengths != nil { + context.setLineDash(phase: dataSet.lineDashPhase, lengths: dataSet.lineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.setLineCap(dataSet.lineCapType) + + // if drawing cubic lines is enabled + switch dataSet.mode { + case .linear: fallthrough + case .stepped: + drawLinear(context: context, dataSet: dataSet) + + case .cubicBezier: + drawCubicBezier(context: context, dataSet: dataSet) + + case .horizontalBezier: + drawHorizontalBezier(context: context, dataSet: dataSet) + } + + context.restoreGState() + } + + @objc open func drawCubicBezier(context: CGContext, dataSet: ILineChartDataSet) { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + // get the color that is specified for this position from the DataSet + let drawingColor = dataSet.colors.first! + + let intensity = dataSet.cubicIntensity + + // the path for the cubic-spline + let cubicPath = CGMutablePath() + + let valueToPixelMatrix = trans.valueToPixelMatrix + + if _xBounds.range >= 1 { + var prevDx: CGFloat = 0.0 + var prevDy: CGFloat = 0.0 + var curDx: CGFloat = 0.0 + var curDy: CGFloat = 0.0 + + // Take an extra point from the left, and an extra from the right. + // That's because we need 4 points for a cubic bezier (cubic=4), otherwise we get lines moving and doing weird stuff on the edges of the chart. + // So in the starting `prev` and `cur`, go -2, -1 + + let firstIndex = _xBounds.min + 1 + + var prevPrev: ChartDataEntry! + var prev: ChartDataEntry! = dataSet.entryForIndex(max(firstIndex - 2, 0)) + var cur: ChartDataEntry! = dataSet.entryForIndex(max(firstIndex - 1, 0)) + var next: ChartDataEntry! = cur + var nextIndex: Int = -1 + + if cur == nil { return } + + // let the spline start + cubicPath.move(to: CGPoint(x: CGFloat(cur.x), y: CGFloat(cur.y * phaseY)), transform: valueToPixelMatrix) + + for j in _xBounds.dropFirst() // same as firstIndex + { + prevPrev = prev + prev = cur + cur = nextIndex == j ? next : dataSet.entryForIndex(j) + + nextIndex = j + 1 < dataSet.entryCount ? j + 1 : j + next = dataSet.entryForIndex(nextIndex) + + if next == nil { break } + + prevDx = CGFloat(cur.x - prevPrev.x) * intensity + prevDy = CGFloat(cur.y - prevPrev.y) * intensity + curDx = CGFloat(next.x - prev.x) * intensity + curDy = CGFloat(next.y - prev.y) * intensity + + cubicPath.addCurve( + to: CGPoint( + x: CGFloat(cur.x), + y: CGFloat(cur.y) * CGFloat(phaseY) + ), + control1: CGPoint( + x: CGFloat(prev.x) + prevDx, + y: (CGFloat(prev.y) + prevDy) * CGFloat(phaseY) + ), + control2: CGPoint( + x: CGFloat(cur.x) - curDx, + y: (CGFloat(cur.y) - curDy) * CGFloat(phaseY) + ), + transform: valueToPixelMatrix + ) + } + } + + context.saveGState() + + if dataSet.isDrawFilledEnabled { + // Copy this path because we make changes to it + let fillPath = cubicPath.mutableCopy() + + drawCubicFill(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix, bounds: _xBounds) + } + + context.beginPath() + context.addPath(cubicPath) + context.setStrokeColor(drawingColor.cgColor) + context.strokePath() + + context.restoreGState() + } + + @objc open func drawHorizontalBezier(context: CGContext, dataSet: ILineChartDataSet) { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + // get the color that is specified for this position from the DataSet + let drawingColor = dataSet.colors.first! + + // the path for the cubic-spline + let cubicPath = CGMutablePath() + + let valueToPixelMatrix = trans.valueToPixelMatrix + + if _xBounds.range >= 1 { + var prev: ChartDataEntry! = dataSet.entryForIndex(_xBounds.min) + var cur: ChartDataEntry! = prev + + if cur == nil { return } + + // let the spline start + cubicPath.move(to: CGPoint(x: CGFloat(cur.x), y: CGFloat(cur.y * phaseY)), transform: valueToPixelMatrix) + + for j in _xBounds.dropFirst() { + prev = cur + cur = dataSet.entryForIndex(j) + + let cpx = CGFloat(prev.x + (cur.x - prev.x) / 2.0) + + cubicPath.addCurve( + to: CGPoint( + x: CGFloat(cur.x), + y: CGFloat(cur.y * phaseY) + ), + control1: CGPoint( + x: cpx, + y: CGFloat(prev.y * phaseY) + ), + control2: CGPoint( + x: cpx, + y: CGFloat(cur.y * phaseY) + ), + transform: valueToPixelMatrix + ) + } + } + + context.saveGState() + + if dataSet.isDrawFilledEnabled { + // Copy this path because we make changes to it + let fillPath = cubicPath.mutableCopy() + + drawCubicFill(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix, bounds: _xBounds) + } + + context.beginPath() + context.addPath(cubicPath) + context.setStrokeColor(drawingColor.cgColor) + context.strokePath() + + context.restoreGState() + } + + open func drawCubicFill( + context: CGContext, + dataSet: ILineChartDataSet, + spline: CGMutablePath, + matrix: CGAffineTransform, + bounds: XBounds + ) { + guard + let dataProvider = dataProvider + else { return } + + if bounds.range <= 0 { + return + } + + let fillMin = dataSet.fillFormatter?.getFillLinePosition(dataSet: dataSet, dataProvider: dataProvider) ?? 0.0 + + var pt1 = CGPoint(x: CGFloat(dataSet.entryForIndex(bounds.min + bounds.range)?.x ?? 0.0), y: fillMin) + var pt2 = CGPoint(x: CGFloat(dataSet.entryForIndex(bounds.min)?.x ?? 0.0), y: fillMin) + pt1 = pt1.applying(matrix) + pt2 = pt2.applying(matrix) + + spline.addLine(to: pt1) + spline.addLine(to: pt2) + spline.closeSubpath() + + if dataSet.fill != nil { + drawFilledPath(context: context, path: spline, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) + } else { + drawFilledPath(context: context, path: spline, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) + } + } + + private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawLinear(context: CGContext, dataSet: ILineChartDataSet) { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let valueToPixelMatrix = trans.valueToPixelMatrix + + let entryCount = dataSet.entryCount + let isDrawSteppedEnabled = dataSet.mode == .stepped + let pointsPerEntryPair = isDrawSteppedEnabled ? 4 : 2 + + let phaseY = animator.phaseY + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + // if drawing filled is enabled + if dataSet.isDrawFilledEnabled, entryCount > 0 { + drawLinearFill(context: context, dataSet: dataSet, trans: trans, bounds: _xBounds) + } + + context.saveGState() + + if _lineSegments.count != pointsPerEntryPair { + // Allocate once in correct size + _lineSegments = [CGPoint](repeating: CGPoint(), count: pointsPerEntryPair) + } + + for j in _xBounds.dropLast() { + var e: ChartDataEntry! = dataSet.entryForIndex(j) + + if e == nil { continue } + + _lineSegments[0].x = CGFloat(e.x) + _lineSegments[0].y = CGFloat(e.y * phaseY) + + if j < _xBounds.max { + // TODO: remove the check. + // With the new XBounds iterator, j is always smaller than _xBounds.max + // Keeping this check for a while, if xBounds have no further breaking changes, it should be safe to remove the check + e = dataSet.entryForIndex(j + 1) + + if e == nil { break } + + if isDrawSteppedEnabled { + _lineSegments[1] = CGPoint(x: CGFloat(e.x), y: _lineSegments[0].y) + _lineSegments[2] = _lineSegments[1] + _lineSegments[3] = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)) + } else { + _lineSegments[1] = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)) + } + } else { + _lineSegments[1] = _lineSegments[0] + } + + for i in 0 ..< _lineSegments.count { + _lineSegments[i] = _lineSegments[i].applying(valueToPixelMatrix) + } + + if !viewPortHandler.isInBoundsRight(_lineSegments[0].x) { + break + } + + // Determine the start and end coordinates of the line, and make sure they differ. + guard + let firstCoordinate = _lineSegments.first, + let lastCoordinate = _lineSegments.last, + firstCoordinate != lastCoordinate else { continue } + + // make sure the lines don't do shitty things outside bounds + if !viewPortHandler.isInBoundsLeft(lastCoordinate.x) || + !viewPortHandler.isInBoundsTop(max(firstCoordinate.y, lastCoordinate.y)) || + !viewPortHandler.isInBoundsBottom(min(firstCoordinate.y, lastCoordinate.y)) + { + continue + } + + // get the color that is set for this line-segment + context.setStrokeColor(dataSet.color(atIndex: j).cgColor) + context.strokeLineSegments(between: _lineSegments) + } + + context.restoreGState() + } + + open func drawLinearFill(context: CGContext, dataSet: ILineChartDataSet, trans: Transformer, bounds: XBounds) { + guard let dataProvider = dataProvider else { return } + + let filled = generateFilledPath( + dataSet: dataSet, + fillMin: dataSet.fillFormatter?.getFillLinePosition(dataSet: dataSet, dataProvider: dataProvider) ?? 0.0, + bounds: bounds, + matrix: trans.valueToPixelMatrix + ) + + if dataSet.fill != nil { + drawFilledPath(context: context, path: filled, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) + } else { + drawFilledPath(context: context, path: filled, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) + } + } + + /// Generates the path that is used for filled drawing. + private func generateFilledPath(dataSet: ILineChartDataSet, fillMin: CGFloat, bounds: XBounds, matrix: CGAffineTransform) -> CGPath { + let phaseY = animator.phaseY + let isDrawSteppedEnabled = dataSet.mode == .stepped + let matrix = matrix + + var e: ChartDataEntry! + + let filled = CGMutablePath() + + e = dataSet.entryForIndex(bounds.min) + if e != nil { + filled.move(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix) + filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix) + } + + // create a new path + for x in stride(from: bounds.min + 1, through: bounds.range + bounds.min, by: 1) { + guard let e = dataSet.entryForIndex(x) else { continue } + + if isDrawSteppedEnabled { + guard let ePrev = dataSet.entryForIndex(x - 1) else { continue } + filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(ePrev.y * phaseY)), transform: matrix) + } + + filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix) + } + + // close up + e = dataSet.entryForIndex(bounds.range + bounds.min) + if e != nil { + filled.addLine(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix) + } + filled.closeSubpath() + + return filled + } + + override open func drawValues(context: CGContext) { + guard + let dataProvider = dataProvider, + let lineData = dataProvider.lineData + else { return } + + if isDrawingValuesAllowed(dataProvider: dataProvider) { + let dataSets = lineData.dataSets + + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0 ..< dataSets.count { + guard let + dataSet = dataSets[i] as? ILineChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let valueFont = dataSet.valueFont + + guard let formatter = dataSet.valueFormatter else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + let iconsOffset = dataSet.iconsOffset + + // make sure the values do not interfear with the circles + var valOffset = Int(dataSet.circleRadius * 1.75) + + if !dataSet.isDrawCirclesEnabled { + valOffset = valOffset / 2 + } + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + for j in _xBounds { + guard let e = dataSet.entryForIndex(j) else { break } + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.y * phaseY) + pt = pt.applying(valueToPixelMatrix) + + if !viewPortHandler.isInBoundsRight(pt.x) { + break + } + + if !viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y) { + continue + } + + if dataSet.isDrawValuesEnabled { + ChartUtils.drawText( + context: context, + text: formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler + ), + point: CGPoint( + x: pt.x, + y: pt.y - CGFloat(valOffset) - valueFont.lineHeight + ), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)] + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + ChartUtils.drawImage(context: context, + image: icon, + x: pt.x + iconsOffset.x, + y: pt.y + iconsOffset.y, + size: icon.size) + } + } + } + } + } + + override open func drawExtras(context: CGContext) { + drawCircles(context: context) + } + + private func drawCircles(context: CGContext) { + guard + let dataProvider = dataProvider, + let lineData = dataProvider.lineData + else { return } + + let phaseY = animator.phaseY + + let dataSets = lineData.dataSets + + var pt = CGPoint() + var rect = CGRect() + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() + + // Make the chart header the first element in the accessible elements array + if let chart = dataProvider as? LineChartView { + let element = createAccessibleHeader(usingChart: chart, + andData: lineData, + withDefaultDescription: "Line Chart") + accessibleChartElements.append(element) + } + + context.saveGState() + + for i in 0 ..< dataSets.count { + guard let dataSet = lineData.getDataSetByIndex(i) as? ILineChartDataSet else { continue } + + if !dataSet.isVisible || dataSet.entryCount == 0 { + continue + } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + let circleRadius = dataSet.circleRadius + let circleDiameter = circleRadius * 2.0 + let circleHoleRadius = dataSet.circleHoleRadius + let circleHoleDiameter = circleHoleRadius * 2.0 + + let drawCircleHole = dataSet.isDrawCircleHoleEnabled && + circleHoleRadius < circleRadius && + circleHoleRadius > 0.0 + let drawTransparentCircleHole = drawCircleHole && + (dataSet.circleHoleColor == nil || + dataSet.circleHoleColor == NSUIColor.clear) + + for j in _xBounds { + guard let e = dataSet.entryForIndex(j) else { break } + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.y * phaseY) + pt = pt.applying(valueToPixelMatrix) + + if !viewPortHandler.isInBoundsRight(pt.x) { + break + } + + // make sure the circles don't do shitty things outside bounds + if !viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y) { + continue + } + + // Skip Circles and Accessibility if not enabled, + // reduces CPU significantly if not needed + if !dataSet.isDrawCirclesEnabled { + continue + } + + // Accessibility element geometry + let scaleFactor: CGFloat = 3 + let accessibilityRect = CGRect(x: pt.x - (scaleFactor * circleRadius), + y: pt.y - (scaleFactor * circleRadius), + width: scaleFactor * circleDiameter, + height: scaleFactor * circleDiameter) + // Create and append the corresponding accessibility element to accessibilityOrderedElements + if let chart = dataProvider as? LineChartView { + let element = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet, + dataSetIndex: i) + { element in + element.accessibilityFrame = accessibilityRect + } + + accessibilityOrderedElements[i].append(element) + } + + context.setFillColor(dataSet.getCircleColor(atIndex: j)!.cgColor) + + rect.origin.x = pt.x - circleRadius + rect.origin.y = pt.y - circleRadius + rect.size.width = circleDiameter + rect.size.height = circleDiameter + + if drawTransparentCircleHole { + // Begin path for circle with hole + context.beginPath() + context.addEllipse(in: rect) + + // Cut hole in path + rect.origin.x = pt.x - circleHoleRadius + rect.origin.y = pt.y - circleHoleRadius + rect.size.width = circleHoleDiameter + rect.size.height = circleHoleDiameter + context.addEllipse(in: rect) + + // Fill in-between + context.fillPath(using: .evenOdd) + } else { + context.fillEllipse(in: rect) + + if drawCircleHole { + context.setFillColor(dataSet.circleHoleColor!.cgColor) + + // The hole rect + rect.origin.x = pt.x - circleHoleRadius + rect.origin.y = pt.y - circleHoleRadius + rect.size.width = circleHoleDiameter + rect.size.height = circleHoleDiameter + + context.fillEllipse(in: rect) + } + } + } + } + + context.restoreGState() + + // Merge nested ordered arrays into the single accessibleChartElements. + accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 }) + accessibilityPostLayoutChangedNotification() + } + + override open func drawHighlighted(context: CGContext, indices: [Highlight]) { + guard + let dataProvider = dataProvider, + let lineData = dataProvider.lineData + else { return } + + let chartXMax = dataProvider.chartXMax + + context.saveGState() + + for high in indices { + guard let set = lineData.getDataSetByIndex(high.dataSetIndex) as? ILineChartDataSet, + set.isHighlightEnabled + else { continue } + + guard let e = set.entryForXValue(high.x, closestToY: high.y) else { continue } + + if !isInBoundsX(entry: e, dataSet: set) { + continue + } + + context.setStrokeColor(set.highlightColor.cgColor) + context.setLineWidth(set.highlightLineWidth) + if set.highlightLineDashLengths != nil { + context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + let x = e.x // get the x-position + let y = e.y * Double(animator.phaseY) + + if x > chartXMax * animator.phaseX { + continue + } + + let trans = dataProvider.getTransformer(forAxis: set.axisDependency) + + let pt = trans.pixelForValues(x: x, y: y) + + high.setDraw(pt: pt) + + // draw the lines + drawHighlightLines(context: context, point: pt, set: set) + } + + context.restoreGState() + } + + /// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements. + /// This is marked internal to support HorizontalBarChartRenderer as well. + private func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] { + guard let chart = dataProvider as? LineChartView else { return [] } + + let dataSetCount = chart.lineData?.dataSetCount ?? 0 + + return Array(repeating: [NSUIAccessibilityElement](), + count: dataSetCount) + } + + /// Creates an NSUIAccessibleElement representing the smallest meaningful bar of the chart + /// i.e. in case of a stacked chart, this returns each stack, not the combined bar. + /// Note that it is marked internal to support subclass modification in the HorizontalBarChart. + private func createAccessibleElement(withIndex idx: Int, + container: LineChartView, + dataSet: ILineChartDataSet, + dataSetIndex: Int, + modifier: (NSUIAccessibilityElement) -> Void) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + let xAxis = container.xAxis + + guard let e = dataSet.entryForIndex(idx) else { return element } + guard let dataProvider = dataProvider else { return element } + + // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. + // i.e. due to the Double conversion, if there are more than one data set that are grouped, + // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. + let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" + + let elementValueText = dataSet.valueFormatter?.stringForValue(e.y, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) ?? "\(e.y)" + + let dataSetCount = dataProvider.lineData?.dataSetCount ?? -1 + let doesContainMultipleDataSets = dataSetCount > 1 + + element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText)" + + modifier(element) + + return element + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/LineRadarRenderer.swift b/Pods/Charts/Source/Charts/Renderers/LineRadarRenderer.swift new file mode 100644 index 0000000..6857805 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/LineRadarRenderer.swift @@ -0,0 +1,49 @@ +// +// LineRadarRenderer.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 CoreGraphics +import Foundation + +@objc(LineRadarChartRenderer) +open class LineRadarRenderer: LineScatterCandleRadarRenderer { + override public init(animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + } + + /// Draws the provided path in filled mode with the provided drawable. + @objc open func drawFilledPath(context: CGContext, path: CGPath, fill: Fill, fillAlpha: CGFloat) { + context.saveGState() + context.beginPath() + context.addPath(path) + + // filled is usually drawn with less alpha + context.setAlpha(fillAlpha) + + fill.fillPath(context: context, rect: viewPortHandler.contentRect) + + context.restoreGState() + } + + /// Draws the provided path in filled mode with the provided color and alpha. + @objc open func drawFilledPath(context: CGContext, path: CGPath, fillColor: NSUIColor, fillAlpha: CGFloat) { + context.saveGState() + context.beginPath() + context.addPath(path) + + // filled is usually drawn with less alpha + context.setAlpha(fillAlpha) + + context.setFillColor(fillColor.cgColor) + context.fillPath() + + context.restoreGState() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift b/Pods/Charts/Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift new file mode 100644 index 0000000..6bf069f --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift @@ -0,0 +1,43 @@ +// +// LineScatterCandleRadarRenderer.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 CoreGraphics +import Foundation + +@objc(LineScatterCandleRadarChartRenderer) +open class LineScatterCandleRadarRenderer: BarLineScatterCandleBubbleRenderer { + override public init(animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + } + + /// Draws vertical & horizontal highlight-lines if enabled. + /// :param: context + /// :param: points + /// :param: horizontal + /// :param: vertical + @objc open func drawHighlightLines(context: CGContext, point: CGPoint, set: ILineScatterCandleRadarChartDataSet) { + // draw vertical highlight lines + if set.isVerticalHighlightIndicatorEnabled { + context.beginPath() + context.move(to: CGPoint(x: point.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: point.x, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + // draw horizontal highlight lines + if set.isHorizontalHighlightIndicatorEnabled { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: point.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: point.y)) + context.strokePath() + } + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/PieChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/PieChartRenderer.swift new file mode 100644 index 0000000..4f8727c --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/PieChartRenderer.swift @@ -0,0 +1,875 @@ +// +// PieChartRenderer.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +open class PieChartRenderer: DataRenderer { + @objc open weak var chart: PieChartView? + + @objc public init(chart: PieChartView, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.chart = chart + } + + override open func drawData(context: CGContext) { + guard let chart = chart else { return } + + let pieData = chart.data + + if pieData != nil { + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + for set in pieData!.dataSets as! [IPieChartDataSet] + where set.isVisible && set.entryCount > 0 + { + drawDataSet(context: context, dataSet: set) + } + } + } + + @objc open func calculateMinimumRadiusForSpacedSlice( + center: CGPoint, + radius: CGFloat, + angle: CGFloat, + arcStartPointX: CGFloat, + arcStartPointY: CGFloat, + startAngle: CGFloat, + sweepAngle: CGFloat + ) -> CGFloat { + let angleMiddle = startAngle + sweepAngle / 2.0 + + // Other point of the arc + let arcEndPointX = center.x + radius * cos((startAngle + sweepAngle).DEG2RAD) + let arcEndPointY = center.y + radius * sin((startAngle + sweepAngle).DEG2RAD) + + // Middle point on the arc + let arcMidPointX = center.x + radius * cos(angleMiddle.DEG2RAD) + let arcMidPointY = center.y + radius * sin(angleMiddle.DEG2RAD) + + // This is the base of the contained triangle + let basePointsDistance = sqrt( + pow(arcEndPointX - arcStartPointX, 2) + + pow(arcEndPointY - arcStartPointY, 2)) + + // After reducing space from both sides of the "slice", + // the angle of the contained triangle should stay the same. + // So let's find out the height of that triangle. + let containedTriangleHeight = (basePointsDistance / 2.0 * + tan((180.0 - angle).DEG2RAD / 2.0)) + + // Now we subtract that from the radius + var spacedRadius = radius - containedTriangleHeight + + // And now subtract the height of the arc that's between the triangle and the outer circle + spacedRadius -= sqrt( + pow(arcMidPointX - (arcEndPointX + arcStartPointX) / 2.0, 2) + + pow(arcMidPointY - (arcEndPointY + arcStartPointY) / 2.0, 2)) + + return spacedRadius + } + + /// Calculates the sliceSpace to use based on visible values and their size compared to the set sliceSpace. + @objc open func getSliceSpace(dataSet: IPieChartDataSet) -> CGFloat { + guard + dataSet.automaticallyDisableSliceSpacing, + let data = chart?.data as? PieChartData + else { return dataSet.sliceSpace } + + let spaceSizeRatio = dataSet.sliceSpace / min(viewPortHandler.contentWidth, viewPortHandler.contentHeight) + let minValueRatio = dataSet.yMin / data.yValueSum * 2.0 + + let sliceSpace = spaceSizeRatio > CGFloat(minValueRatio) + ? 0.0 + : dataSet.sliceSpace + + return sliceSpace + } + + @objc open func drawDataSet(context: CGContext, dataSet: IPieChartDataSet) { + guard let chart = chart else { return } + + var angle: CGFloat = 0.0 + let rotationAngle = chart.rotationAngle + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + let entryCount = dataSet.entryCount + let drawAngles = chart.drawAngles + let center = chart.centerCircleBox + let radius = chart.radius + let drawInnerArc = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled + let userInnerRadius = drawInnerArc ? radius * chart.holeRadiusPercent : 0.0 + + var visibleAngleCount = 0 + for j in 0 ..< entryCount { + guard let e = dataSet.entryForIndex(j) else { continue } + if abs(e.y) > Double.ulpOfOne { + visibleAngleCount += 1 + } + } + + let sliceSpace = visibleAngleCount <= 1 ? 0.0 : getSliceSpace(dataSet: dataSet) + + context.saveGState() + + // Make the chart header the first element in the accessible elements array + // We can do this in drawDataSet, since we know PieChartView can have only 1 dataSet + // Also since there's only 1 dataset, we don't use the typical createAccessibleHeader() here. + // NOTE: - Since we want to summarize the total count of slices/portions/elements, use a default string here + // This is unlike when we are naming individual slices, wherein it's alright to not use a prefix as descriptor. + // i.e. We want to VO to say "3 Elements" even if the developer didn't specify an accessibility prefix + // If prefix is unspecified it is safe to assume they did not want to use "Element 1", so that uses a default empty string + let prefix: String = chart.data?.accessibilityEntryLabelPrefix ?? "Element" + let description = chart.chartDescription?.text ?? dataSet.label ?? chart.centerText ?? "Pie Chart" + + let + element = NSUIAccessibilityElement(accessibilityContainer: chart) + element.accessibilityLabel = description + ". \(entryCount) \(prefix + (entryCount == 1 ? "" : "s"))" + element.accessibilityFrame = chart.bounds + element.isHeader = true + accessibleChartElements.append(element) + + for j in 0 ..< entryCount { + let sliceAngle = drawAngles[j] + var innerRadius = userInnerRadius + + guard let e = dataSet.entryForIndex(j) else { continue } + + defer { + // From here on, even when skipping (i.e for highlight), + // increase the angle + angle += sliceAngle * CGFloat(phaseX) + } + + // draw only if the value is greater than zero + if abs(e.y) < Double.ulpOfOne { continue } + + // Skip if highlighted + if dataSet.isHighlightEnabled, chart.needsHighlight(index: j) { + continue + } + + let accountForSliceSpacing = sliceSpace > 0.0 && sliceAngle <= 180.0 + + context.setFillColor(dataSet.color(atIndex: j).cgColor) + + let sliceSpaceAngleOuter = visibleAngleCount == 1 ? + 0.0 : + sliceSpace / radius.DEG2RAD + let startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.0) * CGFloat(phaseY) + var sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * CGFloat(phaseY) + if sweepAngleOuter < 0.0 { + sweepAngleOuter = 0.0 + } + + let arcStartPointX = center.x + radius * cos(startAngleOuter.DEG2RAD) + let arcStartPointY = center.y + radius * sin(startAngleOuter.DEG2RAD) + + let path = CGMutablePath() + + path.move(to: CGPoint(x: arcStartPointX, + y: arcStartPointY)) + + path.addRelativeArc(center: center, radius: radius, startAngle: startAngleOuter.DEG2RAD, delta: sweepAngleOuter.DEG2RAD) + + if drawInnerArc, + innerRadius > 0.0 || accountForSliceSpacing + { + if accountForSliceSpacing { + var minSpacedRadius = calculateMinimumRadiusForSpacedSlice( + center: center, + radius: radius, + angle: sliceAngle * CGFloat(phaseY), + arcStartPointX: arcStartPointX, + arcStartPointY: arcStartPointY, + startAngle: startAngleOuter, + sweepAngle: sweepAngleOuter + ) + if minSpacedRadius < 0.0 { + minSpacedRadius = -minSpacedRadius + } + innerRadius = min(max(innerRadius, minSpacedRadius), radius) + } + + let sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.0 ? + 0.0 : + sliceSpace / innerRadius.DEG2RAD + let startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.0) * CGFloat(phaseY) + var sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * CGFloat(phaseY) + if sweepAngleInner < 0.0 { + sweepAngleInner = 0.0 + } + let endAngleInner = startAngleInner + sweepAngleInner + + path.addLine( + to: CGPoint( + x: center.x + innerRadius * cos(endAngleInner.DEG2RAD), + y: center.y + innerRadius * sin(endAngleInner.DEG2RAD) + )) + + path.addRelativeArc(center: center, radius: innerRadius, startAngle: endAngleInner.DEG2RAD, delta: -sweepAngleInner.DEG2RAD) + } else { + if accountForSliceSpacing { + let angleMiddle = startAngleOuter + sweepAngleOuter / 2.0 + + let sliceSpaceOffset = + calculateMinimumRadiusForSpacedSlice( + center: center, + radius: radius, + angle: sliceAngle * CGFloat(phaseY), + arcStartPointX: arcStartPointX, + arcStartPointY: arcStartPointY, + startAngle: startAngleOuter, + sweepAngle: sweepAngleOuter + ) + + let arcEndPointX = center.x + sliceSpaceOffset * cos(angleMiddle.DEG2RAD) + let arcEndPointY = center.y + sliceSpaceOffset * sin(angleMiddle.DEG2RAD) + + path.addLine( + to: CGPoint( + x: arcEndPointX, + y: arcEndPointY + )) + } else { + path.addLine(to: center) + } + } + + path.closeSubpath() + + context.beginPath() + context.addPath(path) + context.fillPath(using: .evenOdd) + + let axElement = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet) + { element in + element.accessibilityFrame = path.boundingBoxOfPath + } + + accessibleChartElements.append(axElement) + } + + // Post this notification to let VoiceOver account for the redrawn frames + accessibilityPostLayoutChangedNotification() + + context.restoreGState() + } + + override open func drawValues(context: CGContext) { + guard + let chart = chart, + let data = chart.data + else { return } + + let center = chart.centerCircleBox + + // get whole the radius + let radius = chart.radius + let rotationAngle = chart.rotationAngle + let drawAngles = chart.drawAngles + let absoluteAngles = chart.absoluteAngles + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + var labelRadiusOffset = radius / 10.0 * 3.0 + + if chart.drawHoleEnabled { + labelRadiusOffset = (radius - (radius * chart.holeRadiusPercent)) / 2.0 + } + + let labelRadius = radius - labelRadiusOffset + + let dataSets = data.dataSets + + let yValueSum = (data as! PieChartData).yValueSum + + let drawEntryLabels = chart.isDrawEntryLabelsEnabled + let usePercentValuesEnabled = chart.usePercentValuesEnabled + + var angle: CGFloat = 0.0 + var xIndex = 0 + + context.saveGState() + defer { context.restoreGState() } + + for i in 0 ..< dataSets.count { + guard let dataSet = dataSets[i] as? IPieChartDataSet else { continue } + + let drawValues = dataSet.isDrawValuesEnabled + + if !drawValues, !drawEntryLabels, !dataSet.isDrawIconsEnabled { + continue + } + + let iconsOffset = dataSet.iconsOffset + + let xValuePosition = dataSet.xValuePosition + let yValuePosition = dataSet.yValuePosition + + let valueFont = dataSet.valueFont + let entryLabelFont = dataSet.entryLabelFont ?? chart.entryLabelFont + let lineHeight = valueFont.lineHeight + + guard let formatter = dataSet.valueFormatter else { continue } + + for j in 0 ..< dataSet.entryCount { + guard let e = dataSet.entryForIndex(j) else { continue } + let pe = e as? PieChartDataEntry + + if xIndex == 0 { + angle = 0.0 + } else { + angle = absoluteAngles[xIndex - 1] * CGFloat(phaseX) + } + + let sliceAngle = drawAngles[xIndex] + let sliceSpace = getSliceSpace(dataSet: dataSet) + let sliceSpaceMiddleAngle = sliceSpace / labelRadius.DEG2RAD + + // offset needed to center the drawn text in the slice + let angleOffset = (sliceAngle - sliceSpaceMiddleAngle / 2.0) / 2.0 + + angle = angle + angleOffset + + let transformedAngle = rotationAngle + angle * CGFloat(phaseY) + + let value = usePercentValuesEnabled ? e.y / yValueSum * 100.0 : e.y + let valueText = formatter.stringForValue( + value, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler + ) + + let sliceXBase = cos(transformedAngle.DEG2RAD) + let sliceYBase = sin(transformedAngle.DEG2RAD) + + let drawXOutside = drawEntryLabels && xValuePosition == .outsideSlice + let drawYOutside = drawValues && yValuePosition == .outsideSlice + let drawXInside = drawEntryLabels && xValuePosition == .insideSlice + let drawYInside = drawValues && yValuePosition == .insideSlice + + let valueTextColor = dataSet.valueTextColorAt(j) + let entryLabelColor = dataSet.entryLabelColor ?? chart.entryLabelColor + + if drawXOutside || drawYOutside { + let valueLineLength1 = dataSet.valueLinePart1Length + let valueLineLength2 = dataSet.valueLinePart2Length + let valueLinePart1OffsetPercentage = dataSet.valueLinePart1OffsetPercentage + + var pt2: CGPoint + var labelPoint: CGPoint + var align: NSTextAlignment + + var line1Radius: CGFloat + + if chart.drawHoleEnabled { + line1Radius = (radius - (radius * chart.holeRadiusPercent)) * valueLinePart1OffsetPercentage + (radius * chart.holeRadiusPercent) + } else { + line1Radius = radius * valueLinePart1OffsetPercentage + } + + let polyline2Length = dataSet.valueLineVariableLength + ? labelRadius * valueLineLength2 * abs(sin(transformedAngle.DEG2RAD)) + : labelRadius * valueLineLength2 + + let pt0 = CGPoint( + x: line1Radius * sliceXBase + center.x, + y: line1Radius * sliceYBase + center.y + ) + + let pt1 = CGPoint( + x: labelRadius * (1 + valueLineLength1) * sliceXBase + center.x, + y: labelRadius * (1 + valueLineLength1) * sliceYBase + center.y + ) + + if transformedAngle.truncatingRemainder(dividingBy: 360.0) >= 90.0, transformedAngle.truncatingRemainder(dividingBy: 360.0) <= 270.0 { + pt2 = CGPoint(x: pt1.x - polyline2Length, y: pt1.y) + align = .right + labelPoint = CGPoint(x: pt2.x - 5, y: pt2.y - lineHeight) + } else { + pt2 = CGPoint(x: pt1.x + polyline2Length, y: pt1.y) + align = .left + labelPoint = CGPoint(x: pt2.x + 5, y: pt2.y - lineHeight) + } + + DrawLine: do { + if dataSet.useValueColorForLine { + context.setStrokeColor(dataSet.color(atIndex: j).cgColor) + } else if let valueLineColor = dataSet.valueLineColor { + context.setStrokeColor(valueLineColor.cgColor) + } else { + return + } + context.setLineWidth(dataSet.valueLineWidth) + + context.move(to: CGPoint(x: pt0.x, y: pt0.y)) + context.addLine(to: CGPoint(x: pt1.x, y: pt1.y)) + context.addLine(to: CGPoint(x: pt2.x, y: pt2.y)) + + context.drawPath(using: CGPathDrawingMode.stroke) + } + + if drawXOutside && drawYOutside { + ChartUtils.drawText( + context: context, + text: valueText, + point: labelPoint, + align: align, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + + if j < data.entryCount && pe?.label != nil { + ChartUtils.drawText( + context: context, + text: pe!.label!, + point: CGPoint(x: labelPoint.x, y: labelPoint.y + lineHeight), + align: align, + attributes: [ + NSAttributedString.Key.font: entryLabelFont ?? valueFont, + NSAttributedString.Key.foregroundColor: entryLabelColor ?? valueTextColor, + ] + ) + } + } else if drawXOutside { + if j < data.entryCount && pe?.label != nil { + ChartUtils.drawText( + context: context, + text: pe!.label!, + point: CGPoint(x: labelPoint.x, y: labelPoint.y + lineHeight / 2.0), + align: align, + attributes: [ + NSAttributedString.Key.font: entryLabelFont ?? valueFont, + NSAttributedString.Key.foregroundColor: entryLabelColor ?? valueTextColor, + ] + ) + } + } else if drawYOutside { + ChartUtils.drawText( + context: context, + text: valueText, + point: CGPoint(x: labelPoint.x, y: labelPoint.y + lineHeight / 2.0), + align: align, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + } + } + + if drawXInside || drawYInside { + // calculate the text position + let x = labelRadius * sliceXBase + center.x + let y = labelRadius * sliceYBase + center.y - lineHeight + + if drawXInside && drawYInside { + ChartUtils.drawText( + context: context, + text: valueText, + point: CGPoint(x: x, y: y), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + + if j < data.entryCount && pe?.label != nil { + ChartUtils.drawText( + context: context, + text: pe!.label!, + point: CGPoint(x: x, y: y + lineHeight), + align: .center, + attributes: [ + NSAttributedString.Key.font: entryLabelFont ?? valueFont, + NSAttributedString.Key.foregroundColor: entryLabelColor ?? valueTextColor, + ] + ) + } + } else if drawXInside { + if j < data.entryCount && pe?.label != nil { + ChartUtils.drawText( + context: context, + text: pe!.label!, + point: CGPoint(x: x, y: y + lineHeight / 2.0), + align: .center, + attributes: [ + NSAttributedString.Key.font: entryLabelFont ?? valueFont, + NSAttributedString.Key.foregroundColor: entryLabelColor ?? valueTextColor, + ] + ) + } + } else if drawYInside { + ChartUtils.drawText( + context: context, + text: valueText, + point: CGPoint(x: x, y: y + lineHeight / 2.0), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + } + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + // calculate the icon's position + + let x = (labelRadius + iconsOffset.y) * sliceXBase + center.x + var y = (labelRadius + iconsOffset.y) * sliceYBase + center.y + y += iconsOffset.x + + ChartUtils.drawImage(context: context, + image: icon, + x: x, + y: y, + size: icon.size) + } + + xIndex += 1 + } + } + } + + override open func drawExtras(context: CGContext) { + drawHole(context: context) + drawCenterText(context: context) + } + + /// draws the hole in the center of the chart and the transparent circle / hole + private func drawHole(context: CGContext) { + guard let chart = chart else { return } + + if chart.drawHoleEnabled { + context.saveGState() + + let radius = chart.radius + let holeRadius = radius * chart.holeRadiusPercent + let center = chart.centerCircleBox + + if let holeColor = chart.holeColor { + if holeColor != NSUIColor.clear { + // draw the hole-circle + context.setFillColor(chart.holeColor!.cgColor) + context.fillEllipse(in: CGRect(x: center.x - holeRadius, y: center.y - holeRadius, width: holeRadius * 2.0, height: holeRadius * 2.0)) + } + } + + // only draw the circle if it can be seen (not covered by the hole) + if let transparentCircleColor = chart.transparentCircleColor { + if transparentCircleColor != NSUIColor.clear, + chart.transparentCircleRadiusPercent > chart.holeRadiusPercent + { + let alpha = animator.phaseX * animator.phaseY + let secondHoleRadius = radius * chart.transparentCircleRadiusPercent + + // make transparent + context.setAlpha(CGFloat(alpha)) + context.setFillColor(transparentCircleColor.cgColor) + + // draw the transparent-circle + context.beginPath() + context.addEllipse(in: CGRect( + x: center.x - secondHoleRadius, + y: center.y - secondHoleRadius, + width: secondHoleRadius * 2.0, + height: secondHoleRadius * 2.0 + )) + context.addEllipse(in: CGRect( + x: center.x - holeRadius, + y: center.y - holeRadius, + width: holeRadius * 2.0, + height: holeRadius * 2.0 + )) + context.fillPath(using: .evenOdd) + } + } + + context.restoreGState() + } + } + + /// draws the description text in the center of the pie chart makes most sense when center-hole is enabled + private func drawCenterText(context: CGContext) { + guard + let chart = chart, + let centerAttributedText = chart.centerAttributedText + else { return } + + if chart.drawCenterTextEnabled, centerAttributedText.length > 0 { + let center = chart.centerCircleBox + let offset = chart.centerTextOffset + let innerRadius = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled ? chart.radius * chart.holeRadiusPercent : chart.radius + + let x = center.x + offset.x + let y = center.y + offset.y + + let holeRect = CGRect( + x: x - innerRadius, + y: y - innerRadius, + width: innerRadius * 2.0, + height: innerRadius * 2.0 + ) + var boundingRect = holeRect + + if chart.centerTextRadiusPercent > 0.0 { + boundingRect = boundingRect.insetBy(dx: (boundingRect.width - boundingRect.width * chart.centerTextRadiusPercent) / 2.0, dy: (boundingRect.height - boundingRect.height * chart.centerTextRadiusPercent) / 2.0) + } + + let textBounds = centerAttributedText.boundingRect(with: boundingRect.size, options: [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine], context: nil) + + var drawingRect = boundingRect + drawingRect.origin.x += (boundingRect.size.width - textBounds.size.width) / 2.0 + drawingRect.origin.y += (boundingRect.size.height - textBounds.size.height) / 2.0 + drawingRect.size = textBounds.size + + context.saveGState() + + let clippingPath = CGPath(ellipseIn: holeRect, transform: nil) + context.beginPath() + context.addPath(clippingPath) + context.clip() + + centerAttributedText.draw(with: drawingRect, options: [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine], context: nil) + + context.restoreGState() + } + } + + override open func drawHighlighted(context: CGContext, indices: [Highlight]) { + guard + let chart = chart, + let data = chart.data + else { return } + + context.saveGState() + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + var angle: CGFloat = 0.0 + let rotationAngle = chart.rotationAngle + + let drawAngles = chart.drawAngles + let absoluteAngles = chart.absoluteAngles + let center = chart.centerCircleBox + let radius = chart.radius + let drawInnerArc = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled + let userInnerRadius = drawInnerArc ? radius * chart.holeRadiusPercent : 0.0 + + // Append highlighted accessibility slices into this array, so we can prioritize them over unselected slices + var highlightedAccessibleElements: [NSUIAccessibilityElement] = [] + + for i in 0 ..< indices.count { + // get the index to highlight + let index = Int(indices[i].x) + if index >= drawAngles.count { + continue + } + + guard let set = data.getDataSetByIndex(indices[i].dataSetIndex) as? IPieChartDataSet else { continue } + + if !set.isHighlightEnabled { continue } + + let entryCount = set.entryCount + var visibleAngleCount = 0 + for j in 0 ..< entryCount { + guard let e = set.entryForIndex(j) else { continue } + if abs(e.y) > Double.ulpOfOne { + visibleAngleCount += 1 + } + } + + if index == 0 { + angle = 0.0 + } else { + angle = absoluteAngles[index - 1] * CGFloat(phaseX) + } + + let sliceSpace = visibleAngleCount <= 1 ? 0.0 : set.sliceSpace + + let sliceAngle = drawAngles[index] + var innerRadius = userInnerRadius + + let shift = set.selectionShift + let highlightedRadius = radius + shift + + let accountForSliceSpacing = sliceSpace > 0.0 && sliceAngle <= 180.0 + + context.setFillColor(set.highlightColor?.cgColor ?? set.color(atIndex: index).cgColor) + + let sliceSpaceAngleOuter = visibleAngleCount == 1 ? + 0.0 : + sliceSpace / radius.DEG2RAD + + let sliceSpaceAngleShifted = visibleAngleCount == 1 ? + 0.0 : + sliceSpace / highlightedRadius.DEG2RAD + + let startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.0) * CGFloat(phaseY) + var sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * CGFloat(phaseY) + if sweepAngleOuter < 0.0 { + sweepAngleOuter = 0.0 + } + + let startAngleShifted = rotationAngle + (angle + sliceSpaceAngleShifted / 2.0) * CGFloat(phaseY) + var sweepAngleShifted = (sliceAngle - sliceSpaceAngleShifted) * CGFloat(phaseY) + if sweepAngleShifted < 0.0 { + sweepAngleShifted = 0.0 + } + + let path = CGMutablePath() + + path.move(to: CGPoint(x: center.x + highlightedRadius * cos(startAngleShifted.DEG2RAD), + y: center.y + highlightedRadius * sin(startAngleShifted.DEG2RAD))) + + path.addRelativeArc(center: center, radius: highlightedRadius, startAngle: startAngleShifted.DEG2RAD, + delta: sweepAngleShifted.DEG2RAD) + + var sliceSpaceRadius: CGFloat = 0.0 + if accountForSliceSpacing { + sliceSpaceRadius = calculateMinimumRadiusForSpacedSlice( + center: center, + radius: radius, + angle: sliceAngle * CGFloat(phaseY), + arcStartPointX: center.x + radius * cos(startAngleOuter.DEG2RAD), + arcStartPointY: center.y + radius * sin(startAngleOuter.DEG2RAD), + startAngle: startAngleOuter, + sweepAngle: sweepAngleOuter + ) + } + + if drawInnerArc, + innerRadius > 0.0 || accountForSliceSpacing + { + if accountForSliceSpacing { + var minSpacedRadius = sliceSpaceRadius + if minSpacedRadius < 0.0 { + minSpacedRadius = -minSpacedRadius + } + innerRadius = min(max(innerRadius, minSpacedRadius), radius) + } + + let sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.0 ? + 0.0 : + sliceSpace / innerRadius.DEG2RAD + let startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.0) * CGFloat(phaseY) + var sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * CGFloat(phaseY) + if sweepAngleInner < 0.0 { + sweepAngleInner = 0.0 + } + let endAngleInner = startAngleInner + sweepAngleInner + + path.addLine( + to: CGPoint( + x: center.x + innerRadius * cos(endAngleInner.DEG2RAD), + y: center.y + innerRadius * sin(endAngleInner.DEG2RAD) + )) + + path.addRelativeArc(center: center, radius: innerRadius, + startAngle: endAngleInner.DEG2RAD, + delta: -sweepAngleInner.DEG2RAD) + } else { + if accountForSliceSpacing { + let angleMiddle = startAngleOuter + sweepAngleOuter / 2.0 + + let arcEndPointX = center.x + sliceSpaceRadius * cos(angleMiddle.DEG2RAD) + let arcEndPointY = center.y + sliceSpaceRadius * sin(angleMiddle.DEG2RAD) + + path.addLine( + to: CGPoint( + x: arcEndPointX, + y: arcEndPointY + )) + } else { + path.addLine(to: center) + } + } + + path.closeSubpath() + + context.beginPath() + context.addPath(path) + context.fillPath(using: .evenOdd) + + let axElement = createAccessibleElement(withIndex: index, + container: chart, + dataSet: set) + { element in + element.accessibilityFrame = path.boundingBoxOfPath + element.isSelected = true + } + + highlightedAccessibleElements.append(axElement) + } + + // Prepend selected slices before the already rendered unselected ones. + // NOTE: - This relies on drawDataSet() being called before drawHighlighted in PieChartView. + if !accessibleChartElements.isEmpty { + accessibleChartElements.insert(contentsOf: highlightedAccessibleElements, at: 1) + } + + context.restoreGState() + } + + /// Creates an NSUIAccessibilityElement representing a slice of the PieChart. + /// The element only has it's container and label set based on the chart and dataSet. Use the modifier to alter traits and frame. + private func createAccessibleElement(withIndex idx: Int, + container: PieChartView, + dataSet: IPieChartDataSet, + modifier: (NSUIAccessibilityElement) -> Void) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + + guard let e = dataSet.entryForIndex(idx) else { return element } + guard let formatter = dataSet.valueFormatter else { return element } + guard let data = container.data as? PieChartData else { return element } + + var elementValueText = formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: idx, + viewPortHandler: viewPortHandler + ) + + if container.usePercentValuesEnabled { + let value = e.y / data.yValueSum * 100.0 + let valueText = formatter.stringForValue( + value, + entry: e, + dataSetIndex: idx, + viewPortHandler: viewPortHandler + ) + + elementValueText = valueText + } + + let pieChartDataEntry = (dataSet.entryForIndex(idx) as? PieChartDataEntry) + let isCount = data.accessibilityEntryLabelSuffixIsCount + let prefix = data.accessibilityEntryLabelPrefix?.appending("\(idx + 1)") ?? pieChartDataEntry?.label ?? "" + let suffix = data.accessibilityEntryLabelSuffix ?? "" + element.accessibilityLabel = "\(prefix) : \(elementValueText) \(suffix + (isCount ? (e.y == 1.0 ? "" : "s") : ""))" + + // The modifier allows changing of traits and frame depending on highlight, rotation, etc + modifier(element) + + return element + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/RadarChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/RadarChartRenderer.swift new file mode 100644 index 0000000..30f4e11 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/RadarChartRenderer.swift @@ -0,0 +1,437 @@ +// +// RadarChartRenderer.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 CoreGraphics +import Foundation + +open class RadarChartRenderer: LineRadarRenderer { + private lazy var accessibilityXLabels: [String] = { + guard let chart = chart else { return [] } + guard let formatter = chart.xAxis.valueFormatter else { return [] } + + let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 + return stride(from: 0, to: maxEntryCount, by: 1).map { + formatter.stringForValue(Double($0), axis: chart.xAxis) + } + }() + + @objc open weak var chart: RadarChartView? + + @objc public init(chart: RadarChartView, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.chart = chart + } + + override open func drawData(context: CGContext) { + guard let chart = chart else { return } + + let radarData = chart.data + + if radarData != nil { + let mostEntries = radarData?.maxEntryCountSet?.entryCount ?? 0 + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + // Make the chart header the first element in the accessible elements array + if let accessibilityHeaderData = radarData as? RadarChartData { + let element = createAccessibleHeader(usingChart: chart, + andData: accessibilityHeaderData, + withDefaultDescription: "Radar Chart") + accessibleChartElements.append(element) + } + + for set in radarData!.dataSets as! [IRadarChartDataSet] where set.isVisible { + drawDataSet(context: context, dataSet: set, mostEntries: mostEntries) + } + } + } + + /// Draws the RadarDataSet + /// + /// - Parameters: + /// - context: + /// - dataSet: + /// - mostEntries: the entry count of the dataset with the most entries + internal func drawDataSet(context: CGContext, dataSet: IRadarChartDataSet, mostEntries: Int) { + guard let chart = chart else { return } + + context.saveGState() + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value to pixels + let factor = chart.factor + + let center = chart.centerOffsets + let entryCount = dataSet.entryCount + let path = CGMutablePath() + var hasMovedToPoint = false + + let prefix: String = chart.data?.accessibilityEntryLabelPrefix ?? "Item" + let description = dataSet.label ?? "" + + // Make a tuple of (xLabels, value, originalIndex) then sort it + // This is done, so that the labels are narrated in decreasing order of their corresponding value + // Otherwise, there is no non-visual logic to the data presented + let accessibilityEntryValues = Array(0 ..< entryCount).map { (dataSet.entryForIndex($0)?.y ?? 0, $0) } + let accessibilityAxisLabelValueTuples = zip(accessibilityXLabels, accessibilityEntryValues).map { ($0, $1.0, $1.1) }.sorted { $0.1 > $1.1 } + let accessibilityDataSetDescription: String = description + ". \(entryCount) \(prefix + (entryCount == 1 ? "" : "s")). " + let accessibilityFrameWidth: CGFloat = 22.0 // To allow a tap target of 44x44 + + var accessibilityEntryElements: [NSUIAccessibilityElement] = [] + + for j in 0 ..< entryCount { + guard let e = dataSet.entryForIndex(j) else { continue } + + let p = center.moving(distance: CGFloat((e.y - chart.chartYMin) * Double(factor) * phaseY), + atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) + + if p.x.isNaN { + continue + } + + if !hasMovedToPoint { + path.move(to: p) + hasMovedToPoint = true + } else { + path.addLine(to: p) + } + + let accessibilityLabel = accessibilityAxisLabelValueTuples[j].0 + let accessibilityValue = accessibilityAxisLabelValueTuples[j].1 + let accessibilityValueIndex = accessibilityAxisLabelValueTuples[j].2 + + let axp = center.moving(distance: CGFloat((accessibilityValue - chart.chartYMin) * Double(factor) * phaseY), + atAngle: sliceangle * CGFloat(accessibilityValueIndex) * CGFloat(phaseX) + chart.rotationAngle) + + let axDescription = description + " - " + accessibilityLabel + ": \(accessibilityValue) \(chart.data?.accessibilityEntryLabelSuffix ?? "")" + let axElement = createAccessibleElement(withDescription: axDescription, + container: chart, + dataSet: dataSet) + { element in + element.accessibilityFrame = CGRect(x: axp.x - accessibilityFrameWidth, + y: axp.y - accessibilityFrameWidth, + width: 2 * accessibilityFrameWidth, + height: 2 * accessibilityFrameWidth) + } + + accessibilityEntryElements.append(axElement) + } + + // if this is the largest set, close it + if dataSet.entryCount < mostEntries { + // if this is not the largest set, draw a line to the center before closing + path.addLine(to: center) + } + + path.closeSubpath() + + // draw filled + if dataSet.isDrawFilledEnabled { + if dataSet.fill != nil { + drawFilledPath(context: context, path: path, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) + } else { + drawFilledPath(context: context, path: path, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) + } + } + + // draw the line (only if filled is disabled or alpha is below 255) + if !dataSet.isDrawFilledEnabled || dataSet.fillAlpha < 1.0 { + context.setStrokeColor(dataSet.color(atIndex: 0).cgColor) + context.setLineWidth(dataSet.lineWidth) + context.setAlpha(1.0) + + context.beginPath() + context.addPath(path) + context.strokePath() + + let axElement = createAccessibleElement(withDescription: accessibilityDataSetDescription, + container: chart, + dataSet: dataSet) + { element in + element.isHeader = true + element.accessibilityFrame = path.boundingBoxOfPath + } + + accessibleChartElements.append(axElement) + accessibleChartElements.append(contentsOf: accessibilityEntryElements) + } + + accessibilityPostLayoutChangedNotification() + + context.restoreGState() + } + + override open func drawValues(context: CGContext) { + guard + let chart = chart, + let data = chart.data + else { return } + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value to pixels + let factor = chart.factor + + let center = chart.centerOffsets + + let yoffset = CGFloat(5.0) + + for i in 0 ..< data.dataSetCount { + guard let + dataSet = data.getDataSetByIndex(i) as? IRadarChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let entryCount = dataSet.entryCount + + let iconsOffset = dataSet.iconsOffset + + for j in 0 ..< entryCount { + guard let e = dataSet.entryForIndex(j) else { continue } + + let p = center.moving(distance: CGFloat(e.y - chart.chartYMin) * factor * CGFloat(phaseY), + atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) + + let valueFont = dataSet.valueFont + + guard let formatter = dataSet.valueFormatter else { continue } + + if dataSet.isDrawValuesEnabled { + ChartUtils.drawText( + context: context, + text: formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler + ), + point: CGPoint(x: p.x, y: p.y - yoffset - valueFont.lineHeight), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, + NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)] + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + var pIcon = center.moving(distance: CGFloat(e.y) * factor * CGFloat(phaseY) + iconsOffset.y, + atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) + pIcon.y += iconsOffset.x + + ChartUtils.drawImage(context: context, + image: icon, + x: pIcon.x, + y: pIcon.y, + size: icon.size) + } + } + } + } + + override open func drawExtras(context: CGContext) { + drawWeb(context: context) + } + + private var _webLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawWeb(context: CGContext) { + guard + let chart = chart, + let data = chart.data + else { return } + + let sliceangle = chart.sliceAngle + + context.saveGState() + + // calculate the factor that is needed for transforming the value to + // pixels + let factor = chart.factor + let rotationangle = chart.rotationAngle + + let center = chart.centerOffsets + + // draw the web lines that come from the center + context.setLineWidth(chart.webLineWidth) + context.setStrokeColor(chart.webColor.cgColor) + context.setAlpha(chart.webAlpha) + + let xIncrements = 1 + chart.skipWebLineCount + let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 + + for i in stride(from: 0, to: maxEntryCount, by: xIncrements) { + let p = center.moving(distance: CGFloat(chart.yRange) * factor, + atAngle: sliceangle * CGFloat(i) + rotationangle) + + _webLineSegmentsBuffer[0].x = center.x + _webLineSegmentsBuffer[0].y = center.y + _webLineSegmentsBuffer[1].x = p.x + _webLineSegmentsBuffer[1].y = p.y + + context.strokeLineSegments(between: _webLineSegmentsBuffer) + } + + // draw the inner-web + context.setLineWidth(chart.innerWebLineWidth) + context.setStrokeColor(chart.innerWebColor.cgColor) + context.setAlpha(chart.webAlpha) + + let labelCount = chart.yAxis.entryCount + + for j in 0 ..< labelCount { + for i in 0 ..< data.entryCount { + let r = CGFloat(chart.yAxis.entries[j] - chart.chartYMin) * factor + + let p1 = center.moving(distance: r, atAngle: sliceangle * CGFloat(i) + rotationangle) + let p2 = center.moving(distance: r, atAngle: sliceangle * CGFloat(i + 1) + rotationangle) + + _webLineSegmentsBuffer[0].x = p1.x + _webLineSegmentsBuffer[0].y = p1.y + _webLineSegmentsBuffer[1].x = p2.x + _webLineSegmentsBuffer[1].y = p2.y + + context.strokeLineSegments(between: _webLineSegmentsBuffer) + } + } + + context.restoreGState() + } + + private var _highlightPointBuffer = CGPoint() + + override open func drawHighlighted(context: CGContext, indices: [Highlight]) { + guard + let chart = chart, + let radarData = chart.data as? RadarChartData + else { return } + + context.saveGState() + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value pixels + let factor = chart.factor + + let center = chart.centerOffsets + + for high in indices { + guard + let set = chart.data?.getDataSetByIndex(high.dataSetIndex) as? IRadarChartDataSet, + set.isHighlightEnabled + else { continue } + + guard let e = set.entryForIndex(Int(high.x)) as? RadarChartDataEntry + else { continue } + + if !isInBoundsX(entry: e, dataSet: set) { + continue + } + + context.setLineWidth(radarData.highlightLineWidth) + if radarData.highlightLineDashLengths != nil { + context.setLineDash(phase: radarData.highlightLineDashPhase, lengths: radarData.highlightLineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.setStrokeColor(set.highlightColor.cgColor) + + let y = e.y - chart.chartYMin + + _highlightPointBuffer = center.moving(distance: CGFloat(y) * factor * CGFloat(animator.phaseY), + atAngle: sliceangle * CGFloat(high.x) * CGFloat(animator.phaseX) + chart.rotationAngle) + + high.setDraw(pt: _highlightPointBuffer) + + // draw the lines + drawHighlightLines(context: context, point: _highlightPointBuffer, set: set) + + if set.isDrawHighlightCircleEnabled { + if !_highlightPointBuffer.x.isNaN, !_highlightPointBuffer.y.isNaN { + var strokeColor = set.highlightCircleStrokeColor + if strokeColor == nil { + strokeColor = set.color(atIndex: 0) + } + if set.highlightCircleStrokeAlpha < 1.0 { + strokeColor = strokeColor?.withAlphaComponent(set.highlightCircleStrokeAlpha) + } + + drawHighlightCircle( + context: context, + atPoint: _highlightPointBuffer, + innerRadius: set.highlightCircleInnerRadius, + outerRadius: set.highlightCircleOuterRadius, + fillColor: set.highlightCircleFillColor, + strokeColor: strokeColor, + strokeWidth: set.highlightCircleStrokeWidth + ) + } + } + } + + context.restoreGState() + } + + internal func drawHighlightCircle( + context: CGContext, + atPoint point: CGPoint, + innerRadius: CGFloat, + outerRadius: CGFloat, + fillColor: NSUIColor?, + strokeColor: NSUIColor?, + strokeWidth: CGFloat + ) { + context.saveGState() + + if let fillColor = fillColor { + context.beginPath() + context.addEllipse(in: CGRect(x: point.x - outerRadius, y: point.y - outerRadius, width: outerRadius * 2.0, height: outerRadius * 2.0)) + if innerRadius > 0.0 { + context.addEllipse(in: CGRect(x: point.x - innerRadius, y: point.y - innerRadius, width: innerRadius * 2.0, height: innerRadius * 2.0)) + } + + context.setFillColor(fillColor.cgColor) + context.fillPath(using: .evenOdd) + } + + if let strokeColor = strokeColor { + context.beginPath() + context.addEllipse(in: CGRect(x: point.x - outerRadius, y: point.y - outerRadius, width: outerRadius * 2.0, height: outerRadius * 2.0)) + context.setStrokeColor(strokeColor.cgColor) + context.setLineWidth(strokeWidth) + context.strokePath() + } + + context.restoreGState() + } + + private func createAccessibleElement(withDescription description: String, + container: RadarChartView, + dataSet _: IRadarChartDataSet, + modifier: (NSUIAccessibilityElement) -> Void) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + element.accessibilityLabel = description + + // The modifier allows changing of traits and frame depending on highlight, rotation, etc + modifier(element) + + return element + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/Renderer.swift b/Pods/Charts/Source/Charts/Renderers/Renderer.swift new file mode 100644 index 0000000..6117a9b --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Renderer.swift @@ -0,0 +1,24 @@ +// +// Renderer.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 CoreGraphics +import Foundation + +@objc(ChartRenderer) +open class Renderer: NSObject { + /// the component that handles the drawing area of the chart and it's offsets + @objc public let viewPortHandler: ViewPortHandler + + @objc public init(viewPortHandler: ViewPortHandler) { + self.viewPortHandler = viewPortHandler + super.init() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift b/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift new file mode 100644 index 0000000..d6f523c --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift @@ -0,0 +1,35 @@ +// +import CoreGraphics +// ChevronDownShapeRenderer.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 ChevronDownShapeRenderer: NSObject, IShapeRenderer { + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler _: ViewPortHandler, + point: CGPoint, + color: NSUIColor + ) { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + + context.setLineWidth(1.0) + context.setStrokeColor(color.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: point.x, y: point.y + 2 * shapeHalf)) + context.addLine(to: CGPoint(x: point.x + 2 * shapeHalf, y: point.y)) + context.move(to: CGPoint(x: point.x, y: point.y + 2 * shapeHalf)) + context.addLine(to: CGPoint(x: point.x - 2 * shapeHalf, y: point.y)) + context.strokePath() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift b/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift new file mode 100644 index 0000000..2a48334 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift @@ -0,0 +1,35 @@ +// +import CoreGraphics +// ChevronUpShapeRenderer.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 ChevronUpShapeRenderer: NSObject, IShapeRenderer { + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler _: ViewPortHandler, + point: CGPoint, + color: NSUIColor + ) { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + + context.setLineWidth(1.0) + context.setStrokeColor(color.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: point.x, y: point.y - 2 * shapeHalf)) + context.addLine(to: CGPoint(x: point.x + 2 * shapeHalf, y: point.y)) + context.move(to: CGPoint(x: point.x, y: point.y - 2 * shapeHalf)) + context.addLine(to: CGPoint(x: point.x - 2 * shapeHalf, y: point.y)) + context.strokePath() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift b/Pods/Charts/Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift new file mode 100644 index 0000000..91bd6c8 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift @@ -0,0 +1,58 @@ +// +import CoreGraphics +// CircleShapeRenderer.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 CircleShapeRenderer: NSObject, IShapeRenderer { + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler _: ViewPortHandler, + point: CGPoint, + color: NSUIColor + ) { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + let shapeHoleSizeHalf = dataSet.scatterShapeHoleRadius + let shapeHoleSize = shapeHoleSizeHalf * 2.0 + let shapeHoleColor = dataSet.scatterShapeHoleColor + let shapeStrokeSize = (shapeSize - shapeHoleSize) / 2.0 + let shapeStrokeSizeHalf = shapeStrokeSize / 2.0 + + if shapeHoleSize > 0.0 { + context.setStrokeColor(color.cgColor) + context.setLineWidth(shapeStrokeSize) + var rect = CGRect() + rect.origin.x = point.x - shapeHoleSizeHalf - shapeStrokeSizeHalf + rect.origin.y = point.y - shapeHoleSizeHalf - shapeStrokeSizeHalf + rect.size.width = shapeHoleSize + shapeStrokeSize + rect.size.height = shapeHoleSize + shapeStrokeSize + context.strokeEllipse(in: rect) + + if let shapeHoleColor = shapeHoleColor { + context.setFillColor(shapeHoleColor.cgColor) + rect.origin.x = point.x - shapeHoleSizeHalf + rect.origin.y = point.y - shapeHoleSizeHalf + rect.size.width = shapeHoleSize + rect.size.height = shapeHoleSize + context.fillEllipse(in: rect) + } + } else { + context.setFillColor(color.cgColor) + var rect = CGRect() + rect.origin.x = point.x - shapeHalf + rect.origin.y = point.y - shapeHalf + rect.size.width = shapeSize + rect.size.height = shapeSize + context.fillEllipse(in: rect) + } + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift b/Pods/Charts/Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift new file mode 100644 index 0000000..d4ca9f1 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift @@ -0,0 +1,35 @@ +// +import CoreGraphics +// CrossShapeRenderer.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 CrossShapeRenderer: NSObject, IShapeRenderer { + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler _: ViewPortHandler, + point: CGPoint, + color: NSUIColor + ) { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + + context.setLineWidth(1.0) + context.setStrokeColor(color.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: point.x - shapeHalf, y: point.y)) + context.addLine(to: CGPoint(x: point.x + shapeHalf, y: point.y)) + context.move(to: CGPoint(x: point.x, y: point.y - shapeHalf)) + context.addLine(to: CGPoint(x: point.x, y: point.y + shapeHalf)) + context.strokePath() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/Scatter/IShapeRenderer.swift b/Pods/Charts/Source/Charts/Renderers/Scatter/IShapeRenderer.swift new file mode 100644 index 0000000..39fe871 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Scatter/IShapeRenderer.swift @@ -0,0 +1,32 @@ +// +// IShapeRenderer.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 CoreGraphics +import Foundation + +@objc +public protocol IShapeRenderer: AnyObject { + /// Renders the provided ScatterDataSet with a shape. + /// + /// - Parameters: + /// - context: CGContext for drawing on + /// - dataSet: The DataSet to be drawn + /// - viewPortHandler: Contains information about the current state of the view + /// - point: Position to draw the shape at + /// - color: Color to draw the shape + func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor + ) +} diff --git a/Pods/Charts/Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift b/Pods/Charts/Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift new file mode 100644 index 0000000..5356d35 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift @@ -0,0 +1,58 @@ +// +import CoreGraphics +// SquareShapeRenderer.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 SquareShapeRenderer: NSObject, IShapeRenderer { + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler _: ViewPortHandler, + point: CGPoint, + color: NSUIColor + ) { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + let shapeHoleSizeHalf = dataSet.scatterShapeHoleRadius + let shapeHoleSize = shapeHoleSizeHalf * 2.0 + let shapeHoleColor = dataSet.scatterShapeHoleColor + let shapeStrokeSize = (shapeSize - shapeHoleSize) / 2.0 + let shapeStrokeSizeHalf = shapeStrokeSize / 2.0 + + if shapeHoleSize > 0.0 { + context.setStrokeColor(color.cgColor) + context.setLineWidth(shapeStrokeSize) + var rect = CGRect() + rect.origin.x = point.x - shapeHoleSizeHalf - shapeStrokeSizeHalf + rect.origin.y = point.y - shapeHoleSizeHalf - shapeStrokeSizeHalf + rect.size.width = shapeHoleSize + shapeStrokeSize + rect.size.height = shapeHoleSize + shapeStrokeSize + context.stroke(rect) + + if let shapeHoleColor = shapeHoleColor { + context.setFillColor(shapeHoleColor.cgColor) + rect.origin.x = point.x - shapeHoleSizeHalf + rect.origin.y = point.y - shapeHoleSizeHalf + rect.size.width = shapeHoleSize + rect.size.height = shapeHoleSize + context.fill(rect) + } + } else { + context.setFillColor(color.cgColor) + var rect = CGRect() + rect.origin.x = point.x - shapeHalf + rect.origin.y = point.y - shapeHalf + rect.size.width = shapeSize + rect.size.height = shapeSize + context.fill(rect) + } + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift b/Pods/Charts/Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift new file mode 100644 index 0000000..f7989de --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift @@ -0,0 +1,63 @@ +// +import CoreGraphics +// TriangleShapeRenderer.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 TriangleShapeRenderer: NSObject, IShapeRenderer { + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler _: ViewPortHandler, + point: CGPoint, + color: NSUIColor + ) { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + let shapeHoleSizeHalf = dataSet.scatterShapeHoleRadius + let shapeHoleSize = shapeHoleSizeHalf * 2.0 + let shapeHoleColor = dataSet.scatterShapeHoleColor + let shapeStrokeSize = (shapeSize - shapeHoleSize) / 2.0 + + context.setFillColor(color.cgColor) + + // create a triangle path + context.beginPath() + context.move(to: CGPoint(x: point.x, y: point.y - shapeHalf)) + context.addLine(to: CGPoint(x: point.x + shapeHalf, y: point.y + shapeHalf)) + context.addLine(to: CGPoint(x: point.x - shapeHalf, y: point.y + shapeHalf)) + + if shapeHoleSize > 0.0 { + context.addLine(to: CGPoint(x: point.x, y: point.y - shapeHalf)) + + context.move(to: CGPoint(x: point.x - shapeHalf + shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x + shapeHalf - shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x, y: point.y - shapeHalf + shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x - shapeHalf + shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + } + + context.closePath() + + context.fillPath() + + if shapeHoleSize > 0.0, shapeHoleColor != nil { + context.setFillColor(shapeHoleColor!.cgColor) + + // create a triangle path + context.beginPath() + context.move(to: CGPoint(x: point.x, y: point.y - shapeHalf + shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x + shapeHalf - shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x - shapeHalf + shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + context.closePath() + + context.fillPath() + } + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/Scatter/XShapeRenderer.swift b/Pods/Charts/Source/Charts/Renderers/Scatter/XShapeRenderer.swift new file mode 100644 index 0000000..802915a --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/Scatter/XShapeRenderer.swift @@ -0,0 +1,35 @@ +// +import CoreGraphics +// XShapeRenderer.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 XShapeRenderer: NSObject, IShapeRenderer { + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler _: ViewPortHandler, + point: CGPoint, + color: NSUIColor + ) { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + + context.setLineWidth(1.0) + context.setStrokeColor(color.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: point.x - shapeHalf, y: point.y - shapeHalf)) + context.addLine(to: CGPoint(x: point.x + shapeHalf, y: point.y + shapeHalf)) + context.move(to: CGPoint(x: point.x + shapeHalf, y: point.y - shapeHalf)) + context.addLine(to: CGPoint(x: point.x - shapeHalf, y: point.y + shapeHalf)) + context.strokePath() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/ScatterChartRenderer.swift b/Pods/Charts/Source/Charts/Renderers/ScatterChartRenderer.swift new file mode 100644 index 0000000..25aee1b --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/ScatterChartRenderer.swift @@ -0,0 +1,224 @@ +// +// ScatterChartRenderer.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 CoreGraphics +import Foundation + +open class ScatterChartRenderer: LineScatterCandleRadarRenderer { + @objc open weak var dataProvider: ScatterChartDataProvider? + + @objc public init(dataProvider: ScatterChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + override open func drawData(context: CGContext) { + guard let scatterData = dataProvider?.scatterData else { return } + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + if let chart = dataProvider as? ScatterChartView { + // Make the chart header the first element in the accessible elements array + let element = createAccessibleHeader(usingChart: chart, + andData: scatterData, + withDefaultDescription: "Scatter Chart") + accessibleChartElements.append(element) + } + + // TODO: Due to the potential complexity of data presented in Scatter charts, a more usable way + // for VO accessibility would be to use axis based traversal rather than by dataset. + // Hence, accessibleChartElements is not populated below. (Individual renderers guard against dataSource being their respective views) + for i in 0 ..< scatterData.dataSetCount { + guard let set = scatterData.getDataSetByIndex(i) else { continue } + + if set.isVisible { + if !(set is IScatterChartDataSet) { + fatalError("Datasets for ScatterChartRenderer must conform to IScatterChartDataSet") + } + + drawDataSet(context: context, dataSet: set as! IScatterChartDataSet) + } + } + } + + private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawDataSet(context: CGContext, dataSet: IScatterChartDataSet) { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + let entryCount = dataSet.entryCount + + var point = CGPoint() + + let valueToPixelMatrix = trans.valueToPixelMatrix + + if let renderer = dataSet.shapeRenderer { + context.saveGState() + + for j in 0 ..< Int(min(ceil(Double(entryCount) * animator.phaseX), Double(entryCount))) { + guard let e = dataSet.entryForIndex(j) else { continue } + + point.x = CGFloat(e.x) + point.y = CGFloat(e.y * phaseY) + point = point.applying(valueToPixelMatrix) + + if !viewPortHandler.isInBoundsRight(point.x) { + break + } + + if !viewPortHandler.isInBoundsLeft(point.x) || + !viewPortHandler.isInBoundsY(point.y) + { + continue + } + + renderer.renderShape(context: context, dataSet: dataSet, viewPortHandler: viewPortHandler, point: point, color: dataSet.color(atIndex: j)) + } + + context.restoreGState() + } else { + print("There's no IShapeRenderer specified for ScatterDataSet", terminator: "\n") + } + } + + override open func drawValues(context: CGContext) { + guard + let dataProvider = dataProvider, + let scatterData = dataProvider.scatterData + else { return } + + // if values are drawn + if isDrawingValuesAllowed(dataProvider: dataProvider) { + guard let dataSets = scatterData.dataSets as? [IScatterChartDataSet] else { return } + + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0 ..< scatterData.dataSetCount { + let dataSet = dataSets[i] + guard let + formatter = dataSet.valueFormatter, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let valueFont = dataSet.valueFont + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + let iconsOffset = dataSet.iconsOffset + + let shapeSize = dataSet.scatterShapeSize + let lineHeight = valueFont.lineHeight + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + for j in _xBounds { + guard let e = dataSet.entryForIndex(j) else { break } + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.y * phaseY) + pt = pt.applying(valueToPixelMatrix) + + if !viewPortHandler.isInBoundsRight(pt.x) { + break + } + + // make sure the lines don't do shitty things outside bounds + if !viewPortHandler.isInBoundsLeft(pt.x) + || !viewPortHandler.isInBoundsY(pt.y) + { + continue + } + + let text = formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler + ) + + if dataSet.isDrawValuesEnabled { + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint( + x: pt.x, + y: pt.y - shapeSize - lineHeight + ), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)] + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled { + ChartUtils.drawImage(context: context, + image: icon, + x: pt.x + iconsOffset.x, + y: pt.y + iconsOffset.y, + size: icon.size) + } + } + } + } + } + + override open func drawExtras(context _: CGContext) {} + + override open func drawHighlighted(context: CGContext, indices: [Highlight]) { + guard + let dataProvider = dataProvider, + let scatterData = dataProvider.scatterData + else { return } + + context.saveGState() + + for high in indices { + guard + let set = scatterData.getDataSetByIndex(high.dataSetIndex) as? IScatterChartDataSet, + set.isHighlightEnabled + else { continue } + + guard let entry = set.entryForXValue(high.x, closestToY: high.y) else { continue } + + if !isInBoundsX(entry: entry, dataSet: set) { continue } + + context.setStrokeColor(set.highlightColor.cgColor) + context.setLineWidth(set.highlightLineWidth) + if set.highlightLineDashLengths != nil { + context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + let x = entry.x // get the x-position + let y = entry.y * Double(animator.phaseY) + + let trans = dataProvider.getTransformer(forAxis: set.axisDependency) + + let pt = trans.pixelForValues(x: x, y: y) + + high.setDraw(pt: pt) + + // draw the lines + drawHighlightLines(context: context, point: pt, set: set) + } + + context.restoreGState() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/XAxisRenderer.swift b/Pods/Charts/Source/Charts/Renderers/XAxisRenderer.swift new file mode 100644 index 0000000..c6fe3ed --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/XAxisRenderer.swift @@ -0,0 +1,397 @@ +// +// XAxisRenderer.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +@objc(ChartXAxisRenderer) +open class XAxisRenderer: AxisRendererBase { + @objc public init(viewPortHandler: ViewPortHandler, xAxis: XAxis?, transformer: Transformer?) { + super.init(viewPortHandler: viewPortHandler, transformer: transformer, axis: xAxis) + } + + override open func computeAxis(min: Double, max: Double, inverted: Bool) { + var min = min, max = max + + if let transformer = self.transformer { + // calculate the starting and entry point of the y-labels (depending on + // zoom / contentrect bounds) + if viewPortHandler.contentWidth > 10, !viewPortHandler.isFullyZoomedOutX { + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + + if inverted { + min = Double(p2.x) + max = Double(p1.x) + } else { + min = Double(p1.x) + max = Double(p2.x) + } + } + } + + computeAxisValues(min: min, max: max) + } + + override open func computeAxisValues(min: Double, max: Double) { + super.computeAxisValues(min: min, max: max) + + computeSize() + } + + @objc open func computeSize() { + guard let + xAxis = axis as? XAxis + else { return } + + let longest = xAxis.getLongestLabel() + + let labelSize = longest.size(withAttributes: [NSAttributedString.Key.font: xAxis.labelFont]) + + let labelWidth = labelSize.width + let labelHeight = labelSize.height + + let labelRotatedSize = labelSize.rotatedBy(degrees: xAxis.labelRotationAngle) + + xAxis.labelWidth = labelWidth + xAxis.labelHeight = labelHeight + xAxis.labelRotatedWidth = labelRotatedSize.width + xAxis.labelRotatedHeight = labelRotatedSize.height + } + + override open func renderAxisLabels(context: CGContext) { + guard let xAxis = axis as? XAxis else { return } + + if !xAxis.isEnabled || !xAxis.isDrawLabelsEnabled { + return + } + + let yOffset = xAxis.yOffset + + if xAxis.labelPosition == .top { + drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0)) + } else if xAxis.labelPosition == .topInside { + drawLabels(context: context, pos: viewPortHandler.contentTop + yOffset + xAxis.labelRotatedHeight, anchor: CGPoint(x: 0.5, y: 1.0)) + } else if xAxis.labelPosition == .bottom { + drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0)) + } else if xAxis.labelPosition == .bottomInside { + drawLabels(context: context, pos: viewPortHandler.contentBottom - yOffset - xAxis.labelRotatedHeight, anchor: CGPoint(x: 0.5, y: 0.0)) + } else + { // BOTH SIDED + drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0)) + drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0)) + } + } + + private var _axisLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + override open func renderAxisLine(context: CGContext) { + guard let xAxis = axis as? XAxis else { return } + + if !xAxis.isEnabled || !xAxis.isDrawAxisLineEnabled { + return + } + + context.saveGState() + + context.setStrokeColor(xAxis.axisLineColor.cgColor) + context.setLineWidth(xAxis.axisLineWidth) + if xAxis.axisLineDashLengths != nil { + context.setLineDash(phase: xAxis.axisLineDashPhase, lengths: xAxis.axisLineDashLengths) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + if xAxis.labelPosition == .top + || xAxis.labelPosition == .topInside + || xAxis.labelPosition == .bothSided + { + _axisLineSegmentsBuffer[0].x = viewPortHandler.contentLeft + _axisLineSegmentsBuffer[0].y = viewPortHandler.contentTop + _axisLineSegmentsBuffer[1].x = viewPortHandler.contentRight + _axisLineSegmentsBuffer[1].y = viewPortHandler.contentTop + context.strokeLineSegments(between: _axisLineSegmentsBuffer) + } + + if xAxis.labelPosition == .bottom + || xAxis.labelPosition == .bottomInside + || xAxis.labelPosition == .bothSided + { + _axisLineSegmentsBuffer[0].x = viewPortHandler.contentLeft + _axisLineSegmentsBuffer[0].y = viewPortHandler.contentBottom + _axisLineSegmentsBuffer[1].x = viewPortHandler.contentRight + _axisLineSegmentsBuffer[1].y = viewPortHandler.contentBottom + context.strokeLineSegments(between: _axisLineSegmentsBuffer) + } + + context.restoreGState() + } + + /// draws the x-labels on the specified y-position + @objc open func drawLabels(context: CGContext, pos: CGFloat, anchor: CGPoint) { + guard + let xAxis = axis as? XAxis, + let transformer = self.transformer + else { return } + + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + paraStyle.alignment = .center + + let labelAttrs: [NSAttributedString.Key: Any] = [ + .font: xAxis.labelFont, + .foregroundColor: xAxis.labelTextColor, + .paragraphStyle: paraStyle, + ] + let labelRotationAngleRadians = xAxis.labelRotationAngle.DEG2RAD + + let centeringEnabled = xAxis.isCenterAxisLabelsEnabled + + let valueToPixelMatrix = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + var labelMaxSize = CGSize() + + if xAxis.isWordWrapEnabled { + labelMaxSize.width = xAxis.wordWrapWidthPercent * valueToPixelMatrix.a + } + + let entries = xAxis.entries + + for i in stride(from: 0, to: entries.count, by: 1) { + if centeringEnabled { + position.x = CGFloat(xAxis.centeredEntries[i]) + } else { + position.x = CGFloat(entries[i]) + } + + position.y = 0.0 + position = position.applying(valueToPixelMatrix) + + if viewPortHandler.isInBoundsX(position.x) { + let label = xAxis.valueFormatter?.stringForValue(xAxis.entries[i], axis: xAxis) ?? "" + + let labelns = label as NSString + + if xAxis.isAvoidFirstLastClippingEnabled { + // avoid clipping of the last + if i == xAxis.entryCount - 1, xAxis.entryCount > 1 { + let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width + + if width > viewPortHandler.offsetRight * 2.0, + position.x + width > viewPortHandler.chartWidth + { + position.x -= width / 2.0 + } + } else if i == 0 + { // avoid clipping of the first + let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width + position.x += width / 2.0 + } + } + + drawLabel(context: context, + formattedLabel: label, + x: position.x, + y: pos, + attributes: labelAttrs, + constrainedToSize: labelMaxSize, + anchor: anchor, + angleRadians: labelRotationAngleRadians) + } + } + } + + @objc open func drawLabel( + context: CGContext, + formattedLabel: String, + x: CGFloat, + y: CGFloat, + attributes: [NSAttributedString.Key: Any], + constrainedToSize: CGSize, + anchor: CGPoint, + angleRadians: CGFloat + ) { + ChartUtils.drawMultilineText( + context: context, + text: formattedLabel, + point: CGPoint(x: x, y: y), + attributes: attributes, + constrainedToSize: constrainedToSize, + anchor: anchor, + angleRadians: angleRadians + ) + } + + override open func renderGridLines(context: CGContext) { + guard + let xAxis = axis as? XAxis, + let transformer = self.transformer + else { return } + + if !xAxis.isDrawGridLinesEnabled || !xAxis.isEnabled { + return + } + + context.saveGState() + defer { context.restoreGState() } + context.clip(to: gridClippingRect) + + context.setShouldAntialias(xAxis.gridAntialiasEnabled) + context.setStrokeColor(xAxis.gridColor.cgColor) + context.setLineWidth(xAxis.gridLineWidth) + context.setLineCap(xAxis.gridLineCap) + + if xAxis.gridLineDashLengths != nil { + context.setLineDash(phase: xAxis.gridLineDashPhase, lengths: xAxis.gridLineDashLengths) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + let valueToPixelMatrix = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + let entries = xAxis.entries + + for i in stride(from: 0, to: entries.count, by: 1) { + position.x = CGFloat(entries[i]) + position.y = position.x + position = position.applying(valueToPixelMatrix) + + drawGridLine(context: context, x: position.x, y: position.y) + } + } + + @objc open var gridClippingRect: CGRect { + var contentRect = viewPortHandler.contentRect + let dx = axis?.gridLineWidth ?? 0.0 + contentRect.origin.x -= dx / 2.0 + contentRect.size.width += dx + return contentRect + } + + @objc open func drawGridLine(context: CGContext, x: CGFloat, y _: CGFloat) { + if x >= viewPortHandler.offsetLeft, + x <= viewPortHandler.chartWidth + { + context.beginPath() + context.move(to: CGPoint(x: x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: x, y: viewPortHandler.contentBottom)) + context.strokePath() + } + } + + override open func renderLimitLines(context: CGContext) { + guard + let xAxis = axis as? XAxis, + let transformer = self.transformer, + !xAxis.limitLines.isEmpty + else { return } + + let trans = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + for l in xAxis.limitLines where l.isEnabled { + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.x -= l.lineWidth / 2.0 + clippingRect.size.width += l.lineWidth + context.clip(to: clippingRect) + + position.x = CGFloat(l.limit) + position.y = 0.0 + position = position.applying(trans) + + renderLimitLineLine(context: context, limitLine: l, position: position) + renderLimitLineLabel(context: context, limitLine: l, position: position, yOffset: 2.0 + l.yOffset) + } + } + + @objc open func renderLimitLineLine(context: CGContext, limitLine: ChartLimitLine, position: CGPoint) { + context.beginPath() + context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) + + context.setStrokeColor(limitLine.lineColor.cgColor) + context.setLineWidth(limitLine.lineWidth) + if limitLine.lineDashLengths != nil { + context.setLineDash(phase: limitLine.lineDashPhase, lengths: limitLine.lineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + } + + @objc open func renderLimitLineLabel(context: CGContext, limitLine: ChartLimitLine, position: CGPoint, yOffset: CGFloat) { + let label = limitLine.label + guard limitLine.drawLabelEnabled, !label.isEmpty else { return } + + let labelLineHeight = limitLine.valueFont.lineHeight + + let xOffset: CGFloat = limitLine.lineWidth + limitLine.xOffset + let attributes: [NSAttributedString.Key: Any] = [ + .font: limitLine.valueFont, + .foregroundColor: limitLine.valueTextColor, + ] + + let (point, align): (CGPoint, NSTextAlignment) + switch limitLine.labelPosition { + case .topRight: + point = CGPoint( + x: position.x + xOffset, + y: viewPortHandler.contentTop + yOffset + ) + align = .left + + case .bottomRight: + point = CGPoint( + x: position.x + xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset + ) + align = .left + + case .topLeft: + point = CGPoint( + x: position.x - xOffset, + y: viewPortHandler.contentTop + yOffset + ) + align = .right + + case .bottomLeft: + point = CGPoint( + x: position.x - xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset + ) + align = .right + } + + ChartUtils.drawText( + context: context, + text: label, + point: point, + align: align, + attributes: attributes + ) + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift b/Pods/Charts/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift new file mode 100644 index 0000000..11fc06f --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift @@ -0,0 +1,313 @@ +// +// XAxisRendererHorizontalBarChart.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 CoreGraphics +import Foundation + +open class XAxisRendererHorizontalBarChart: XAxisRenderer { + internal weak var chart: BarChartView? + + @objc public init(viewPortHandler: ViewPortHandler, xAxis: XAxis?, transformer: Transformer?, chart: BarChartView) { + super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: transformer) + + self.chart = chart + } + + override open func computeAxis(min: Double, max: Double, inverted: Bool) { + var min = min, max = max + + if let transformer = self.transformer { + // calculate the starting and entry point of the y-labels (depending on + // zoom / contentrect bounds) + if viewPortHandler.contentWidth > 10, !viewPortHandler.isFullyZoomedOutY { + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + + if inverted { + min = Double(p2.y) + max = Double(p1.y) + } else { + min = Double(p1.y) + max = Double(p2.y) + } + } + } + + computeAxisValues(min: min, max: max) + } + + override open func computeSize() { + guard let + xAxis = axis as? XAxis + else { return } + + let longest = xAxis.getLongestLabel() as NSString + + let labelSize = longest.size(withAttributes: [NSAttributedString.Key.font: xAxis.labelFont]) + + let labelWidth = floor(labelSize.width + xAxis.xOffset * 3.5) + let labelHeight = labelSize.height + let labelRotatedSize = CGSize(width: labelSize.width, height: labelHeight).rotatedBy(degrees: xAxis.labelRotationAngle) + + xAxis.labelWidth = labelWidth + xAxis.labelHeight = labelHeight + xAxis.labelRotatedWidth = round(labelRotatedSize.width + xAxis.xOffset * 3.5) + xAxis.labelRotatedHeight = round(labelRotatedSize.height) + } + + override open func renderAxisLabels(context: CGContext) { + guard + let xAxis = axis as? XAxis + else { return } + + if !xAxis.isEnabled || !xAxis.isDrawLabelsEnabled || chart?.data === nil { + return + } + + let xoffset = xAxis.xOffset + + if xAxis.labelPosition == .top { + drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) + } else if xAxis.labelPosition == .topInside { + drawLabels(context: context, pos: viewPortHandler.contentRight - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) + } else if xAxis.labelPosition == .bottom { + drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) + } else if xAxis.labelPosition == .bottomInside { + drawLabels(context: context, pos: viewPortHandler.contentLeft + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) + } else + { // BOTH SIDED + drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) + drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) + } + } + + /// draws the x-labels on the specified y-position + override open func drawLabels(context: CGContext, pos: CGFloat, anchor: CGPoint) { + guard + let xAxis = axis as? XAxis, + let transformer = self.transformer + else { return } + + let labelFont = xAxis.labelFont + let labelTextColor = xAxis.labelTextColor + let labelRotationAngleRadians = xAxis.labelRotationAngle.DEG2RAD + + let centeringEnabled = xAxis.isCenterAxisLabelsEnabled + + // pre allocate to save performance (dont allocate in loop) + var position = CGPoint(x: 0.0, y: 0.0) + + for i in stride(from: 0, to: xAxis.entryCount, by: 1) { + // only fill x values + + position.x = 0.0 + + if centeringEnabled { + position.y = CGFloat(xAxis.centeredEntries[i]) + } else { + position.y = CGFloat(xAxis.entries[i]) + } + + transformer.pointValueToPixel(&position) + + if viewPortHandler.isInBoundsY(position.y) { + if let label = xAxis.valueFormatter?.stringForValue(xAxis.entries[i], axis: xAxis) { + drawLabel( + context: context, + formattedLabel: label, + x: pos, + y: position.y, + attributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: labelTextColor], + anchor: anchor, + angleRadians: labelRotationAngleRadians + ) + } + } + } + } + + @objc open func drawLabel( + context: CGContext, + formattedLabel: String, + x: CGFloat, + y: CGFloat, + attributes: [NSAttributedString.Key: Any], + anchor: CGPoint, + angleRadians: CGFloat + ) { + ChartUtils.drawText( + context: context, + text: formattedLabel, + point: CGPoint(x: x, y: y), + attributes: attributes, + anchor: anchor, + angleRadians: angleRadians + ) + } + + override open var gridClippingRect: CGRect { + var contentRect = viewPortHandler.contentRect + let dy = axis?.gridLineWidth ?? 0.0 + contentRect.origin.y -= dy / 2.0 + contentRect.size.height += dy + return contentRect + } + + private var _gridLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + override open func drawGridLine(context: CGContext, x _: CGFloat, y: CGFloat) { + if viewPortHandler.isInBoundsY(y) { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: y)) + context.strokePath() + } + } + + override open func renderAxisLine(context: CGContext) { + guard let xAxis = axis as? XAxis else { return } + + if !xAxis.isEnabled || !xAxis.isDrawAxisLineEnabled { + return + } + + context.saveGState() + + context.setStrokeColor(xAxis.axisLineColor.cgColor) + context.setLineWidth(xAxis.axisLineWidth) + if xAxis.axisLineDashLengths != nil { + context.setLineDash(phase: xAxis.axisLineDashPhase, lengths: xAxis.axisLineDashLengths) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + if xAxis.labelPosition == .top || + xAxis.labelPosition == .topInside || + xAxis.labelPosition == .bothSided + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + if xAxis.labelPosition == .bottom || + xAxis.labelPosition == .bottomInside || + xAxis.labelPosition == .bothSided + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + context.restoreGState() + } + + override open func renderLimitLines(context: CGContext) { + guard + let xAxis = axis as? XAxis, + let transformer = self.transformer + else { return } + + let limitLines = xAxis.limitLines + + if limitLines.count == 0 { + return + } + + let trans = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + for i in 0 ..< limitLines.count { + let l = limitLines[i] + + if !l.isEnabled { + continue + } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.y -= l.lineWidth / 2.0 + clippingRect.size.height += l.lineWidth + context.clip(to: clippingRect) + + position.x = 0.0 + position.y = CGFloat(l.limit) + position = position.applying(trans) + + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: position.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: position.y)) + + context.setStrokeColor(l.lineColor.cgColor) + context.setLineWidth(l.lineWidth) + if l.lineDashLengths != nil { + context.setLineDash(phase: l.lineDashPhase, lengths: l.lineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + + let label = l.label + + // if drawing the limit-value label is enabled + if l.drawLabelEnabled, label.count > 0 { + let labelLineHeight = l.valueFont.lineHeight + + let xOffset: CGFloat = 4.0 + l.xOffset + let yOffset: CGFloat = l.lineWidth + labelLineHeight + l.yOffset + + if l.labelPosition == .topRight { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentRight - xOffset, + y: position.y - yOffset + ), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else if l.labelPosition == .bottomRight { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentRight - xOffset, + y: position.y + yOffset - labelLineHeight + ), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else if l.labelPosition == .topLeft { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentLeft + xOffset, + y: position.y - yOffset + ), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentLeft + xOffset, + y: position.y + yOffset - labelLineHeight + ), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + } + } + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/XAxisRendererRadarChart.swift b/Pods/Charts/Source/Charts/Renderers/XAxisRendererRadarChart.swift new file mode 100644 index 0000000..21ecacb --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/XAxisRendererRadarChart.swift @@ -0,0 +1,85 @@ +// +// XAxisRendererRadarChart.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 CoreGraphics +import Foundation + +open class XAxisRendererRadarChart: XAxisRenderer { + @objc open weak var chart: RadarChartView? + + @objc public init(viewPortHandler: ViewPortHandler, xAxis: XAxis?, chart: RadarChartView) { + super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: nil) + + self.chart = chart + } + + override open func renderAxisLabels(context: CGContext) { + guard let + xAxis = axis as? XAxis, + let chart = chart + else { return } + + if !xAxis.isEnabled || !xAxis.isDrawLabelsEnabled { + return + } + + let labelFont = xAxis.labelFont + let labelTextColor = xAxis.labelTextColor + let labelRotationAngleRadians = xAxis.labelRotationAngle.RAD2DEG + let drawLabelAnchor = CGPoint(x: 0.5, y: 0.25) + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value to pixels + let factor = chart.factor + + let center = chart.centerOffsets + + for i in stride(from: 0, to: chart.data?.maxEntryCountSet?.entryCount ?? 0, by: 1) { + let label = xAxis.valueFormatter?.stringForValue(Double(i), axis: xAxis) ?? "" + + let angle = (sliceangle * CGFloat(i) + chart.rotationAngle).truncatingRemainder(dividingBy: 360.0) + + let p = center.moving(distance: CGFloat(chart.yRange) * factor + xAxis.labelRotatedWidth / 2.0, atAngle: angle) + + drawLabel(context: context, + formattedLabel: label, + x: p.x, + y: p.y - xAxis.labelRotatedHeight / 2.0, + attributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: labelTextColor], + anchor: drawLabelAnchor, + angleRadians: labelRotationAngleRadians) + } + } + + @objc open func drawLabel( + context: CGContext, + formattedLabel: String, + x: CGFloat, + y: CGFloat, + attributes: [NSAttributedString.Key: Any], + anchor: CGPoint, + angleRadians: CGFloat + ) { + ChartUtils.drawText( + context: context, + text: formattedLabel, + point: CGPoint(x: x, y: y), + attributes: attributes, + anchor: anchor, + angleRadians: angleRadians + ) + } + + override open func renderLimitLines(context _: CGContext) { + /// XAxis LimitLines on RadarChart not yet supported. + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/YAxisRenderer.swift b/Pods/Charts/Source/Charts/Renderers/YAxisRenderer.swift new file mode 100644 index 0000000..fb1e0c0 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/YAxisRenderer.swift @@ -0,0 +1,348 @@ +// +// YAxisRenderer.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +@objc(ChartYAxisRenderer) +open class YAxisRenderer: AxisRendererBase { + @objc public init(viewPortHandler: ViewPortHandler, yAxis: YAxis?, transformer: Transformer?) { + super.init(viewPortHandler: viewPortHandler, transformer: transformer, axis: yAxis) + } + + /// draws the y-axis labels to the screen + override open func renderAxisLabels(context: CGContext) { + guard let yAxis = axis as? YAxis else { return } + + if !yAxis.isEnabled || !yAxis.isDrawLabelsEnabled { + return + } + + let xoffset = yAxis.xOffset + let yoffset = yAxis.labelFont.lineHeight / 2.5 + yAxis.yOffset + + let dependency = yAxis.axisDependency + let labelPosition = yAxis.labelPosition + + var xPos = CGFloat(0.0) + + var textAlign: NSTextAlignment + + if dependency == .left { + if labelPosition == .outsideChart { + textAlign = .right + xPos = viewPortHandler.offsetLeft - xoffset + } else { + textAlign = .left + xPos = viewPortHandler.offsetLeft + xoffset + } + } else { + if labelPosition == .outsideChart { + textAlign = .left + xPos = viewPortHandler.contentRight + xoffset + } else { + textAlign = .right + xPos = viewPortHandler.contentRight - xoffset + } + } + + drawYLabels( + context: context, + fixedPosition: xPos, + positions: transformedPositions(), + offset: yoffset - yAxis.labelFont.lineHeight, + textAlign: textAlign + ) + } + + override open func renderAxisLine(context: CGContext) { + guard let yAxis = axis as? YAxis else { return } + + if !yAxis.isEnabled || !yAxis.drawAxisLineEnabled { + return + } + + context.saveGState() + + context.setStrokeColor(yAxis.axisLineColor.cgColor) + context.setLineWidth(yAxis.axisLineWidth) + if yAxis.axisLineDashLengths != nil { + context.setLineDash(phase: yAxis.axisLineDashPhase, lengths: yAxis.axisLineDashLengths) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + if yAxis.axisDependency == .left { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + context.strokePath() + } else { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + context.restoreGState() + } + + /// draws the y-labels on the specified x-position + open func drawYLabels( + context: CGContext, + fixedPosition: CGFloat, + positions: [CGPoint], + offset: CGFloat, + textAlign: NSTextAlignment + ) { + guard + let yAxis = axis as? YAxis + else { return } + + let labelFont = yAxis.labelFont + let labelTextColor = yAxis.labelTextColor + + let from = yAxis.isDrawBottomYLabelEntryEnabled ? 0 : 1 + let to = yAxis.isDrawTopYLabelEntryEnabled ? yAxis.entryCount : (yAxis.entryCount - 1) + + let xOffset = yAxis.labelXOffset + + for i in stride(from: from, to: to, by: 1) { + let text = yAxis.getFormattedLabel(i) + + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint(x: fixedPosition + xOffset, y: positions[i].y + offset), + align: textAlign, + attributes: [.font: labelFont, .foregroundColor: labelTextColor] + ) + } + } + + override open func renderGridLines(context: CGContext) { + guard let + yAxis = axis as? YAxis + else { return } + + if !yAxis.isEnabled { + return + } + + if yAxis.drawGridLinesEnabled { + let positions = transformedPositions() + + context.saveGState() + defer { context.restoreGState() } + context.clip(to: gridClippingRect) + + context.setShouldAntialias(yAxis.gridAntialiasEnabled) + context.setStrokeColor(yAxis.gridColor.cgColor) + context.setLineWidth(yAxis.gridLineWidth) + context.setLineCap(yAxis.gridLineCap) + + if yAxis.gridLineDashLengths != nil { + context.setLineDash(phase: yAxis.gridLineDashPhase, lengths: yAxis.gridLineDashLengths) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + // draw the grid + positions.forEach { drawGridLine(context: context, position: $0) } + } + + if yAxis.drawZeroLineEnabled { + // draw zero line + drawZeroLine(context: context) + } + } + + @objc open var gridClippingRect: CGRect { + var contentRect = viewPortHandler.contentRect + let dy = axis?.gridLineWidth ?? 0.0 + contentRect.origin.y -= dy / 2.0 + contentRect.size.height += dy + return contentRect + } + + @objc open func drawGridLine( + context: CGContext, + position: CGPoint + ) { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: position.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: position.y)) + context.strokePath() + } + + @objc open func transformedPositions() -> [CGPoint] { + guard + let yAxis = axis as? YAxis, + let transformer = self.transformer + else { return [CGPoint]() } + + var positions = [CGPoint]() + positions.reserveCapacity(yAxis.entryCount) + + let entries = yAxis.entries + + for i in stride(from: 0, to: yAxis.entryCount, by: 1) { + positions.append(CGPoint(x: 0.0, y: entries[i])) + } + + transformer.pointValuesToPixel(&positions) + + return positions + } + + /// Draws the zero line at the specified position. + @objc open func drawZeroLine(context: CGContext) { + guard + let yAxis = axis as? YAxis, + let transformer = self.transformer, + let zeroLineColor = yAxis.zeroLineColor + else { return } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.y -= yAxis.zeroLineWidth / 2.0 + clippingRect.size.height += yAxis.zeroLineWidth + context.clip(to: clippingRect) + + context.setStrokeColor(zeroLineColor.cgColor) + context.setLineWidth(yAxis.zeroLineWidth) + + let pos = transformer.pixelForValues(x: 0.0, y: 0.0) + + if yAxis.zeroLineDashLengths != nil { + context.setLineDash(phase: yAxis.zeroLineDashPhase, lengths: yAxis.zeroLineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: pos.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: pos.y)) + context.drawPath(using: CGPathDrawingMode.stroke) + } + + override open func renderLimitLines(context: CGContext) { + guard + let yAxis = axis as? YAxis, + let transformer = self.transformer + else { return } + + let limitLines = yAxis.limitLines + + if limitLines.count == 0 { + return + } + + context.saveGState() + + let trans = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + for i in 0 ..< limitLines.count { + let l = limitLines[i] + + if !l.isEnabled { + continue + } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.y -= l.lineWidth / 2.0 + clippingRect.size.height += l.lineWidth + context.clip(to: clippingRect) + + position.x = 0.0 + position.y = CGFloat(l.limit) + position = position.applying(trans) + + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: position.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: position.y)) + + context.setStrokeColor(l.lineColor.cgColor) + context.setLineWidth(l.lineWidth) + if l.lineDashLengths != nil { + context.setLineDash(phase: l.lineDashPhase, lengths: l.lineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + + let label = l.label + + // if drawing the limit-value label is enabled + if l.drawLabelEnabled, label.count > 0 { + let labelLineHeight = l.valueFont.lineHeight + + let xOffset: CGFloat = 4.0 + l.xOffset + let yOffset: CGFloat = l.lineWidth + labelLineHeight + l.yOffset + + if l.labelPosition == .topRight { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentRight - xOffset, + y: position.y - yOffset + ), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else if l.labelPosition == .bottomRight { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentRight - xOffset, + y: position.y + yOffset - labelLineHeight + ), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else if l.labelPosition == .topLeft { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentLeft + xOffset, + y: position.y - yOffset + ), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentLeft + xOffset, + y: position.y + yOffset - labelLineHeight + ), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + } + } + + context.restoreGState() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift b/Pods/Charts/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift new file mode 100644 index 0000000..194eba1 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift @@ -0,0 +1,326 @@ +// +// YAxisRendererHorizontalBarChart.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 CoreGraphics +import Foundation + +open class YAxisRendererHorizontalBarChart: YAxisRenderer { + override public init(viewPortHandler: ViewPortHandler, yAxis: YAxis?, transformer: Transformer?) { + super.init(viewPortHandler: viewPortHandler, yAxis: yAxis, transformer: transformer) + } + + /// Computes the axis values. + override open func computeAxis(min: Double, max: Double, inverted: Bool) { + guard let transformer = self.transformer else { return } + + var min = min, max = max + + // calculate the starting and entry point of the y-labels (depending on zoom / contentrect bounds) + if viewPortHandler.contentHeight > 10.0, !viewPortHandler.isFullyZoomedOutX { + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + + if !inverted { + min = Double(p1.x) + max = Double(p2.x) + } else { + min = Double(p2.x) + max = Double(p1.x) + } + } + + computeAxisValues(min: min, max: max) + } + + /// draws the y-axis labels to the screen + override open func renderAxisLabels(context: CGContext) { + guard let yAxis = axis as? YAxis else { return } + + if !yAxis.isEnabled || !yAxis.isDrawLabelsEnabled { + return + } + + let lineHeight = yAxis.labelFont.lineHeight + let baseYOffset: CGFloat = 2.5 + + let dependency = yAxis.axisDependency + let labelPosition = yAxis.labelPosition + + var yPos: CGFloat = 0.0 + + if dependency == .left { + if labelPosition == .outsideChart { + yPos = viewPortHandler.contentTop - baseYOffset + } else { + yPos = viewPortHandler.contentTop - baseYOffset + } + } else { + if labelPosition == .outsideChart { + yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset + } else { + yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset + } + } + + // For compatibility with Android code, we keep above calculation the same, + // And here we pull the line back up + yPos -= lineHeight + + drawYLabels( + context: context, + fixedPosition: yPos, + positions: transformedPositions(), + offset: yAxis.yOffset + ) + } + + override open func renderAxisLine(context: CGContext) { + guard let yAxis = axis as? YAxis else { return } + + if !yAxis.isEnabled || !yAxis.drawAxisLineEnabled { + return + } + + context.saveGState() + + context.setStrokeColor(yAxis.axisLineColor.cgColor) + context.setLineWidth(yAxis.axisLineWidth) + if yAxis.axisLineDashLengths != nil { + context.setLineDash(phase: yAxis.axisLineDashPhase, lengths: yAxis.axisLineDashLengths) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + if yAxis.axisDependency == .left { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + context.strokePath() + } else { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + context.restoreGState() + } + + /// draws the y-labels on the specified x-position + @objc open func drawYLabels( + context: CGContext, + fixedPosition: CGFloat, + positions: [CGPoint], + offset: CGFloat + ) { + guard let + yAxis = axis as? YAxis + else { return } + + let labelFont = yAxis.labelFont + let labelTextColor = yAxis.labelTextColor + + let from = yAxis.isDrawBottomYLabelEntryEnabled ? 0 : 1 + let to = yAxis.isDrawTopYLabelEntryEnabled ? yAxis.entryCount : (yAxis.entryCount - 1) + + let xOffset = yAxis.labelXOffset + + for i in stride(from: from, to: to, by: 1) { + let text = yAxis.getFormattedLabel(i) + + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint( + x: positions[i].x, y: + fixedPosition - offset + xOffset + ), + align: .center, + attributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: labelTextColor] + ) + } + } + + override open var gridClippingRect: CGRect { + var contentRect = viewPortHandler.contentRect + let dx = self.axis?.gridLineWidth ?? 0.0 + contentRect.origin.x -= dx / 2.0 + contentRect.size.width += dx + return contentRect + } + + override open func drawGridLine( + context: CGContext, + position: CGPoint + ) { + context.beginPath() + context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + override open func transformedPositions() -> [CGPoint] { + guard + let yAxis = axis as? YAxis, + let transformer = self.transformer + else { return [CGPoint]() } + + var positions = [CGPoint]() + positions.reserveCapacity(yAxis.entryCount) + + let entries = yAxis.entries + + for i in stride(from: 0, to: yAxis.entryCount, by: 1) { + positions.append(CGPoint(x: entries[i], y: 0.0)) + } + + transformer.pointValuesToPixel(&positions) + + return positions + } + + /// Draws the zero line at the specified position. + override open func drawZeroLine(context: CGContext) { + guard + let yAxis = axis as? YAxis, + let transformer = self.transformer, + let zeroLineColor = yAxis.zeroLineColor + else { return } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.x -= yAxis.zeroLineWidth / 2.0 + clippingRect.size.width += yAxis.zeroLineWidth + context.clip(to: clippingRect) + + context.setStrokeColor(zeroLineColor.cgColor) + context.setLineWidth(yAxis.zeroLineWidth) + + let pos = transformer.pixelForValues(x: 0.0, y: 0.0) + + if yAxis.zeroLineDashLengths != nil { + context.setLineDash(phase: yAxis.zeroLineDashPhase, lengths: yAxis.zeroLineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.move(to: CGPoint(x: pos.x - 1.0, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: pos.x - 1.0, y: viewPortHandler.contentBottom)) + context.drawPath(using: CGPathDrawingMode.stroke) + } + + private var _limitLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + override open func renderLimitLines(context: CGContext) { + guard + let yAxis = axis as? YAxis, + let transformer = self.transformer + else { return } + + let limitLines = yAxis.limitLines + + if limitLines.count <= 0 { + return + } + + context.saveGState() + + let trans = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + for i in 0 ..< limitLines.count { + let l = limitLines[i] + + if !l.isEnabled { + continue + } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.x -= l.lineWidth / 2.0 + clippingRect.size.width += l.lineWidth + context.clip(to: clippingRect) + + position.x = CGFloat(l.limit) + position.y = 0.0 + position = position.applying(trans) + + context.beginPath() + context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) + + context.setStrokeColor(l.lineColor.cgColor) + context.setLineWidth(l.lineWidth) + if l.lineDashLengths != nil { + context.setLineDash(phase: l.lineDashPhase, lengths: l.lineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + + let label = l.label + + // if drawing the limit-value label is enabled + if l.drawLabelEnabled, label.count > 0 { + let labelLineHeight = l.valueFont.lineHeight + + let xOffset: CGFloat = l.lineWidth + l.xOffset + let yOffset: CGFloat = 2.0 + l.yOffset + + if l.labelPosition == .topRight { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: position.x + xOffset, + y: viewPortHandler.contentTop + yOffset + ), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else if l.labelPosition == .bottomRight { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: position.x + xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset + ), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else if l.labelPosition == .topLeft { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: position.x - xOffset, + y: viewPortHandler.contentTop + yOffset + ), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } else { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: position.x - xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset + ), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + } + } + + context.restoreGState() + } +} diff --git a/Pods/Charts/Source/Charts/Renderers/YAxisRendererRadarChart.swift b/Pods/Charts/Source/Charts/Renderers/YAxisRendererRadarChart.swift new file mode 100644 index 0000000..76ea488 --- /dev/null +++ b/Pods/Charts/Source/Charts/Renderers/YAxisRendererRadarChart.swift @@ -0,0 +1,248 @@ +// +// YAxisRendererRadarChart.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +open class YAxisRendererRadarChart: YAxisRenderer { + private weak var chart: RadarChartView? + + @objc public init(viewPortHandler: ViewPortHandler, yAxis: YAxis?, chart: RadarChartView) { + super.init(viewPortHandler: viewPortHandler, yAxis: yAxis, transformer: nil) + + self.chart = chart + } + + override open func computeAxisValues(min yMin: Double, max yMax: Double) { + guard let + axis = axis as? YAxis + else { return } + + let labelCount = axis.labelCount + let range = abs(yMax - yMin) + + if labelCount == 0 || range <= 0 || range.isInfinite { + axis.entries = [Double]() + axis.centeredEntries = [Double]() + return + } + + // Find out how much spacing (in yValue space) between axis values + let rawInterval = range / Double(labelCount) + var interval = rawInterval.roundedToNextSignficant() + + // If granularity is enabled, then do not allow the interval to go below specified granularity. + // This is used to avoid repeated values when rounding values for display. + if axis.isGranularityEnabled { + interval = interval < axis.granularity ? axis.granularity : interval + } + + // Normalize interval + let intervalMagnitude = pow(10.0, floor(log10(interval))).roundedToNextSignficant() + let intervalSigDigit = Int(interval / intervalMagnitude) + + if intervalSigDigit > 5 { + // Use one order of magnitude higher, to avoid intervals like 0.9 or 90 + // if it's 0.0 after floor(), we use the old value + interval = floor(10.0 * intervalMagnitude) == 0.0 ? interval : floor(10.0 * intervalMagnitude) + } + + let centeringEnabled = axis.isCenterAxisLabelsEnabled + var n = centeringEnabled ? 1 : 0 + + // force label count + if axis.isForceLabelsEnabled { + let step = Double(range) / Double(labelCount - 1) + + // Ensure stops contains at least n elements. + axis.entries.removeAll(keepingCapacity: true) + axis.entries.reserveCapacity(labelCount) + + var v = yMin + + for _ in 0 ..< labelCount { + axis.entries.append(v) + v += step + } + + n = labelCount + } else { + // no forced count + + var first = interval == 0.0 ? 0.0 : ceil(yMin / interval) * interval + + if centeringEnabled { + first -= interval + } + + let last = interval == 0.0 ? 0.0 : (floor(yMax / interval) * interval).nextUp + + if interval != 0.0 { + for _ in stride(from: first, through: last, by: interval) { + n += 1 + } + } + + n += 1 + + // Ensure stops contains at least n elements. + axis.entries.removeAll(keepingCapacity: true) + axis.entries.reserveCapacity(labelCount) + + var f = first + var i = 0 + while i < n { + if f == 0.0 { + // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) + f = 0.0 + } + + axis.entries.append(Double(f)) + + f += interval + i += 1 + } + } + + // set decimals + if interval < 1 { + axis.decimals = Int(ceil(-log10(interval))) + } else { + axis.decimals = 0 + } + + if centeringEnabled { + axis.centeredEntries.reserveCapacity(n) + axis.centeredEntries.removeAll() + + let offset = (axis.entries[1] - axis.entries[0]) / 2.0 + + for i in 0 ..< n { + axis.centeredEntries.append(axis.entries[i] + offset) + } + } + + axis._axisMinimum = axis.entries[0] + axis._axisMaximum = axis.entries[n - 1] + axis.axisRange = abs(axis._axisMaximum - axis._axisMinimum) + } + + override open func renderAxisLabels(context: CGContext) { + guard let + yAxis = axis as? YAxis, + let chart = chart + else { return } + + if !yAxis.isEnabled || !yAxis.isDrawLabelsEnabled { + return + } + + let labelFont = yAxis.labelFont + let labelTextColor = yAxis.labelTextColor + + let center = chart.centerOffsets + let factor = chart.factor + + let labelLineHeight = yAxis.labelFont.lineHeight + + let from = yAxis.isDrawBottomYLabelEntryEnabled ? 0 : 1 + let to = yAxis.isDrawTopYLabelEntryEnabled ? yAxis.entryCount : (yAxis.entryCount - 1) + + let alignment: NSTextAlignment = yAxis.labelAlignment + let xOffset = yAxis.labelXOffset + + for j in stride(from: from, to: to, by: 1) { + let r = CGFloat(yAxis.entries[j] - yAxis._axisMinimum) * factor + + let p = center.moving(distance: r, atAngle: chart.rotationAngle) + + let label = yAxis.getFormattedLabel(j) + + ChartUtils.drawText( + context: context, + text: label, + point: CGPoint(x: p.x + xOffset, y: p.y - labelLineHeight), + align: alignment, + attributes: [ + NSAttributedString.Key.font: labelFont, + NSAttributedString.Key.foregroundColor: labelTextColor, + ] + ) + } + } + + override open func renderLimitLines(context: CGContext) { + guard + let yAxis = axis as? YAxis, + let chart = chart, + let data = chart.data + else { return } + + let limitLines = yAxis.limitLines + + if limitLines.count == 0 { + return + } + + context.saveGState() + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value to pixels + let factor = chart.factor + + let center = chart.centerOffsets + + for i in 0 ..< limitLines.count { + let l = limitLines[i] + + if !l.isEnabled { + continue + } + + context.setStrokeColor(l.lineColor.cgColor) + context.setLineWidth(l.lineWidth) + if l.lineDashLengths != nil { + context.setLineDash(phase: l.lineDashPhase, lengths: l.lineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + let r = CGFloat(l.limit - chart.chartYMin) * factor + + context.beginPath() + + for j in 0 ..< (data.maxEntryCountSet?.entryCount ?? 0) { + let p = center.moving(distance: r, atAngle: sliceangle * CGFloat(j) + chart.rotationAngle) + + if j == 0 { + context.move(to: CGPoint(x: p.x, y: p.y)) + } else { + context.addLine(to: CGPoint(x: p.x, y: p.y)) + } + } + + context.closePath() + + context.strokePath() + } + + context.restoreGState() + } +} diff --git a/Pods/Charts/Source/Charts/Utils/ChartColorTemplates.swift b/Pods/Charts/Source/Charts/Utils/ChartColorTemplates.swift new file mode 100644 index 0000000..ce163b2 --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/ChartColorTemplates.swift @@ -0,0 +1,178 @@ +// +// ChartColorTemplates.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 CoreGraphics +import Foundation + +open class ChartColorTemplates: NSObject { + @objc open class func liberty() -> [NSUIColor] { + [ + NSUIColor(red: 207 / 255.0, green: 248 / 255.0, blue: 246 / 255.0, alpha: 1.0), + NSUIColor(red: 148 / 255.0, green: 212 / 255.0, blue: 212 / 255.0, alpha: 1.0), + NSUIColor(red: 136 / 255.0, green: 180 / 255.0, blue: 187 / 255.0, alpha: 1.0), + NSUIColor(red: 118 / 255.0, green: 174 / 255.0, blue: 175 / 255.0, alpha: 1.0), + NSUIColor(red: 42 / 255.0, green: 109 / 255.0, blue: 130 / 255.0, alpha: 1.0), + ] + } + + @objc open class func joyful() -> [NSUIColor] { + [ + NSUIColor(red: 217 / 255.0, green: 80 / 255.0, blue: 138 / 255.0, alpha: 1.0), + NSUIColor(red: 254 / 255.0, green: 149 / 255.0, blue: 7 / 255.0, alpha: 1.0), + NSUIColor(red: 254 / 255.0, green: 247 / 255.0, blue: 120 / 255.0, alpha: 1.0), + NSUIColor(red: 106 / 255.0, green: 167 / 255.0, blue: 134 / 255.0, alpha: 1.0), + NSUIColor(red: 53 / 255.0, green: 194 / 255.0, blue: 209 / 255.0, alpha: 1.0), + ] + } + + @objc open class func pastel() -> [NSUIColor] { + [ + NSUIColor(red: 64 / 255.0, green: 89 / 255.0, blue: 128 / 255.0, alpha: 1.0), + NSUIColor(red: 149 / 255.0, green: 165 / 255.0, blue: 124 / 255.0, alpha: 1.0), + NSUIColor(red: 217 / 255.0, green: 184 / 255.0, blue: 162 / 255.0, alpha: 1.0), + NSUIColor(red: 191 / 255.0, green: 134 / 255.0, blue: 134 / 255.0, alpha: 1.0), + NSUIColor(red: 179 / 255.0, green: 48 / 255.0, blue: 80 / 255.0, alpha: 1.0), + ] + } + + @objc open class func colorful() -> [NSUIColor] { + [ + NSUIColor(red: 193 / 255.0, green: 37 / 255.0, blue: 82 / 255.0, alpha: 1.0), + NSUIColor(red: 255 / 255.0, green: 102 / 255.0, blue: 0 / 255.0, alpha: 1.0), + NSUIColor(red: 245 / 255.0, green: 199 / 255.0, blue: 0 / 255.0, alpha: 1.0), + NSUIColor(red: 106 / 255.0, green: 150 / 255.0, blue: 31 / 255.0, alpha: 1.0), + NSUIColor(red: 179 / 255.0, green: 100 / 255.0, blue: 53 / 255.0, alpha: 1.0), + ] + } + + @objc open class func vordiplom() -> [NSUIColor] { + [ + NSUIColor(red: 192 / 255.0, green: 255 / 255.0, blue: 140 / 255.0, alpha: 1.0), + NSUIColor(red: 255 / 255.0, green: 247 / 255.0, blue: 140 / 255.0, alpha: 1.0), + NSUIColor(red: 255 / 255.0, green: 208 / 255.0, blue: 140 / 255.0, alpha: 1.0), + NSUIColor(red: 140 / 255.0, green: 234 / 255.0, blue: 255 / 255.0, alpha: 1.0), + NSUIColor(red: 255 / 255.0, green: 140 / 255.0, blue: 157 / 255.0, alpha: 1.0), + ] + } + + @objc open class func material() -> [NSUIColor] { + [ + NSUIColor(red: 46 / 255.0, green: 204 / 255.0, blue: 113 / 255.0, alpha: 1.0), + NSUIColor(red: 241 / 255.0, green: 196 / 255.0, blue: 15 / 255.0, alpha: 1.0), + NSUIColor(red: 231 / 255.0, green: 76 / 255.0, blue: 60 / 255.0, alpha: 1.0), + NSUIColor(red: 52 / 255.0, green: 152 / 255.0, blue: 219 / 255.0, alpha: 1.0), + ] + } + + @objc open class func colorFromString(_ colorString: String) -> NSUIColor { + let leftParenCharset = CharacterSet(charactersIn: "( ") + let commaCharset = CharacterSet(charactersIn: ", ") + + let colorString = colorString.lowercased() + + if colorString.hasPrefix("#") { + var argb: [UInt] = [255, 0, 0, 0] + let colorString = colorString.unicodeScalars + var length = colorString.count + var index = colorString.startIndex + let endIndex = colorString.endIndex + + index = colorString.index(after: index) + length = length - 1 + + if length == 3 || length == 6 || length == 8 { + var i = length == 8 ? 0 : 1 + while index < endIndex { + var c = colorString[index] + index = colorString.index(after: index) + + var val = (c.value >= 0x61 && c.value <= 0x66) ? (c.value - 0x61 + 10) : c.value - 0x30 + argb[i] = UInt(val) * 16 + if length == 3 { + argb[i] = argb[i] + UInt(val) + } else { + c = colorString[index] + index = colorString.index(after: index) + + val = (c.value >= 0x61 && c.value <= 0x66) ? (c.value - 0x61 + 10) : c.value - 0x30 + argb[i] = argb[i] + UInt(val) + } + + i += 1 + } + } + + return NSUIColor(red: CGFloat(argb[1]) / 255.0, green: CGFloat(argb[2]) / 255.0, blue: CGFloat(argb[3]) / 255.0, alpha: CGFloat(argb[0]) / 255.0) + } else if colorString.hasPrefix("rgba") { + var a: Float = 1.0 + var r: Int32 = 0 + var g: Int32 = 0 + var b: Int32 = 0 + let scanner = Scanner(string: colorString) + scanner.scanString("rgba", into: nil) + scanner.scanCharacters(from: leftParenCharset, into: nil) + scanner.scanInt32(&r) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&g) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&b) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanFloat(&a) + return NSUIColor( + red: CGFloat(r) / 255.0, + green: CGFloat(g) / 255.0, + blue: CGFloat(b) / 255.0, + alpha: CGFloat(a) + ) + } else if colorString.hasPrefix("argb") { + var a: Float = 1.0 + var r: Int32 = 0 + var g: Int32 = 0 + var b: Int32 = 0 + let scanner = Scanner(string: colorString) + scanner.scanString("argb", into: nil) + scanner.scanCharacters(from: leftParenCharset, into: nil) + scanner.scanFloat(&a) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&r) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&g) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&b) + return NSUIColor( + red: CGFloat(r) / 255.0, + green: CGFloat(g) / 255.0, + blue: CGFloat(b) / 255.0, + alpha: CGFloat(a) + ) + } else if colorString.hasPrefix("rgb") { + var r: Int32 = 0 + var g: Int32 = 0 + var b: Int32 = 0 + let scanner = Scanner(string: colorString) + scanner.scanString("rgb", into: nil) + scanner.scanCharacters(from: leftParenCharset, into: nil) + scanner.scanInt32(&r) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&g) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&b) + return NSUIColor( + red: CGFloat(r) / 255.0, + green: CGFloat(g) / 255.0, + blue: CGFloat(b) / 255.0, + alpha: 1.0 + ) + } + + return NSUIColor.clear + } +} diff --git a/Pods/Charts/Source/Charts/Utils/ChartUtils.swift b/Pods/Charts/Source/Charts/Utils/ChartUtils.swift new file mode 100644 index 0000000..0d6683c --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/ChartUtils.swift @@ -0,0 +1,268 @@ +// +// Utils.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 CoreGraphics +import Foundation + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) + import Cocoa +#endif + +extension Comparable { + func clamped(to range: ClosedRange) -> Self { + if self > range.upperBound { + return range.upperBound + } else if self < range.lowerBound { + return range.lowerBound + } else { + return self + } + } +} + +extension FloatingPoint { + var DEG2RAD: Self { + self * .pi / 180 + } + + var RAD2DEG: Self { + self * 180 / .pi + } + + /// - Note: Value must be in degrees + /// - Returns: An angle between 0.0 < 360.0 (not less than zero, less than 360) + var normalizedAngle: Self { + let angle = truncatingRemainder(dividingBy: 360) + return (sign == .minus) ? angle + 360 : angle + } +} + +extension CGSize { + func rotatedBy(degrees: CGFloat) -> CGSize { + let radians = degrees.DEG2RAD + return rotatedBy(radians: radians) + } + + func rotatedBy(radians: CGFloat) -> CGSize { + CGSize( + width: abs(width * cos(radians)) + abs(height * sin(radians)), + height: abs(width * sin(radians)) + abs(height * cos(radians)) + ) + } +} + +extension Double { + /// Rounds the number to the nearest multiple of it's order of magnitude, rounding away from zero if halfway. + func roundedToNextSignficant() -> Double { + guard + !isInfinite, + !isNaN, + self != 0 + else { return self } + + let d = ceil(log10(self < 0 ? -self : self)) + let pw = 1 - Int(d) + let magnitude = pow(10.0, Double(pw)) + let shifted = (self * magnitude).rounded() + return shifted / magnitude + } + + var decimalPlaces: Int { + guard + !isNaN, + !isInfinite, + self != 0.0 + else { return 0 } + + let i = roundedToNextSignficant() + + guard + !i.isInfinite, + !i.isNaN + else { return 0 } + + return Int(ceil(-log10(i))) + 2 + } +} + +extension CGPoint { + /// Calculates the position around a center point, depending on the distance from the center, and the angle of the position around the center. + func moving(distance: CGFloat, atAngle angle: CGFloat) -> CGPoint { + CGPoint(x: x + distance * cos(angle.DEG2RAD), + y: y + distance * sin(angle.DEG2RAD)) + } +} + +open class ChartUtils { + private static var _defaultValueFormatter: IValueFormatter = ChartUtils.generateDefaultValueFormatter() + + open class func drawImage( + context: CGContext, + image: NSUIImage, + x: CGFloat, + y: CGFloat, + size: CGSize + ) { + var drawOffset = CGPoint() + drawOffset.x = x - (size.width / 2) + drawOffset.y = y - (size.height / 2) + + NSUIGraphicsPushContext(context) + + if image.size.width != size.width, image.size.height != size.height { + let key = "resized_\(size.width)_\(size.height)" + + // Try to take scaled image from cache of this image + var scaledImage = objc_getAssociatedObject(image, key) as? NSUIImage + if scaledImage == nil { + // Scale the image + NSUIGraphicsBeginImageContextWithOptions(size, false, 0.0) + + image.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size)) + + scaledImage = NSUIGraphicsGetImageFromCurrentImageContext() + NSUIGraphicsEndImageContext() + + // Put the scaled image in a cache owned by the original image + objc_setAssociatedObject(image, key, scaledImage, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + + scaledImage?.draw(in: CGRect(origin: drawOffset, size: size)) + } else { + image.draw(in: CGRect(origin: drawOffset, size: size)) + } + + NSUIGraphicsPopContext() + } + + open class func drawText(context: CGContext, text: String, point: CGPoint, align: NSTextAlignment, attributes: [NSAttributedString.Key: Any]?) { + var point = point + + if align == .center { + point.x -= text.size(withAttributes: attributes).width / 2.0 + } else if align == .right { + point.x -= text.size(withAttributes: attributes).width + } + + NSUIGraphicsPushContext(context) + + (text as NSString).draw(at: point, withAttributes: attributes) + + NSUIGraphicsPopContext() + } + + open class func drawText(context: CGContext, text: String, point: CGPoint, attributes: [NSAttributedString.Key: Any]?, anchor: CGPoint, angleRadians: CGFloat) { + var drawOffset = CGPoint() + + NSUIGraphicsPushContext(context) + + if angleRadians != 0.0 { + let size = text.size(withAttributes: attributes) + + // Move the text drawing rect in a way that it always rotates around its center + drawOffset.x = -size.width * 0.5 + drawOffset.y = -size.height * 0.5 + + var translate = point + + // Move the "outer" rect relative to the anchor, assuming its centered + if anchor.x != 0.5 || anchor.y != 0.5 { + let rotatedSize = size.rotatedBy(radians: angleRadians) + + translate.x -= rotatedSize.width * (anchor.x - 0.5) + translate.y -= rotatedSize.height * (anchor.y - 0.5) + } + + context.saveGState() + context.translateBy(x: translate.x, y: translate.y) + context.rotate(by: angleRadians) + + (text as NSString).draw(at: drawOffset, withAttributes: attributes) + + context.restoreGState() + } else { + if anchor.x != 0.0 || anchor.y != 0.0 { + let size = text.size(withAttributes: attributes) + + drawOffset.x = -size.width * anchor.x + drawOffset.y = -size.height * anchor.y + } + + drawOffset.x += point.x + drawOffset.y += point.y + + (text as NSString).draw(at: drawOffset, withAttributes: attributes) + } + + NSUIGraphicsPopContext() + } + + internal class func drawMultilineText(context: CGContext, text: String, knownTextSize: CGSize, point: CGPoint, attributes: [NSAttributedString.Key: Any]?, constrainedToSize _: CGSize, anchor: CGPoint, angleRadians: CGFloat) { + var rect = CGRect(origin: CGPoint(), size: knownTextSize) + + NSUIGraphicsPushContext(context) + + if angleRadians != 0.0 { + // Move the text drawing rect in a way that it always rotates around its center + rect.origin.x = -knownTextSize.width * 0.5 + rect.origin.y = -knownTextSize.height * 0.5 + + var translate = point + + // Move the "outer" rect relative to the anchor, assuming its centered + if anchor.x != 0.5 || anchor.y != 0.5 { + let rotatedSize = knownTextSize.rotatedBy(radians: angleRadians) + + translate.x -= rotatedSize.width * (anchor.x - 0.5) + translate.y -= rotatedSize.height * (anchor.y - 0.5) + } + + context.saveGState() + context.translateBy(x: translate.x, y: translate.y) + context.rotate(by: angleRadians) + + (text as NSString).draw(with: rect, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) + + context.restoreGState() + } else { + if anchor.x != 0.0 || anchor.y != 0.0 { + rect.origin.x = -knownTextSize.width * anchor.x + rect.origin.y = -knownTextSize.height * anchor.y + } + + rect.origin.x += point.x + rect.origin.y += point.y + + (text as NSString).draw(with: rect, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) + } + + NSUIGraphicsPopContext() + } + + internal class func drawMultilineText(context: CGContext, text: String, point: CGPoint, attributes: [NSAttributedString.Key: Any]?, constrainedToSize: CGSize, anchor: CGPoint, angleRadians: CGFloat) { + let rect = text.boundingRect(with: constrainedToSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) + drawMultilineText(context: context, text: text, knownTextSize: rect.size, point: point, attributes: attributes, constrainedToSize: constrainedToSize, anchor: anchor, angleRadians: angleRadians) + } + + private class func generateDefaultValueFormatter() -> IValueFormatter { + let formatter = DefaultValueFormatter(decimals: 1) + return formatter + } + + /// - Returns: The default value formatter used for all chart components that needs a default + open class func defaultValueFormatter() -> IValueFormatter { + _defaultValueFormatter + } +} diff --git a/Pods/Charts/Source/Charts/Utils/Fill.swift b/Pods/Charts/Source/Charts/Utils/Fill.swift new file mode 100644 index 0000000..09c6a06 --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/Fill.swift @@ -0,0 +1,285 @@ +// +// Fill.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 CoreGraphics +import Foundation + +@objc(ChartFillType) +public enum FillType: Int { + case empty + case color + case linearGradient + case radialGradient + case image + case tiledImage + case layer +} + +@objc(ChartFill) +open class Fill: NSObject { + private var _type = FillType.empty + private var _color: CGColor? + private var _gradient: CGGradient? + private var _gradientAngle: CGFloat = 0.0 + private var _gradientStartOffsetPercent = CGPoint() + private var _gradientStartRadiusPercent: CGFloat = 0.0 + private var _gradientEndOffsetPercent = CGPoint() + private var _gradientEndRadiusPercent: CGFloat = 0.0 + private var _image: CGImage? + private var _layer: CGLayer? + + // MARK: Properties + + @objc open var type: FillType { + _type + } + + @objc open var color: CGColor? { + _color + } + + @objc open var gradient: CGGradient? { + _gradient + } + + @objc open var gradientAngle: CGFloat { + _gradientAngle + } + + @objc open var gradientStartOffsetPercent: CGPoint { + _gradientStartOffsetPercent + } + + @objc open var gradientStartRadiusPercent: CGFloat { + _gradientStartRadiusPercent + } + + @objc open var gradientEndOffsetPercent: CGPoint { + _gradientEndOffsetPercent + } + + @objc open var gradientEndRadiusPercent: CGFloat { + _gradientEndRadiusPercent + } + + @objc open var image: CGImage? { + _image + } + + @objc open var layer: CGLayer? { + _layer + } + + // MARK: Constructors + + override public init() {} + + @objc public init(CGColor: CGColor) { + _type = .color + _color = CGColor + } + + @objc public convenience init(color: NSUIColor) { + self.init(CGColor: color.cgColor) + } + + @objc public init(linearGradient: CGGradient, angle: CGFloat) { + _type = .linearGradient + _gradient = linearGradient + _gradientAngle = angle + } + + @objc public init( + radialGradient: CGGradient, + startOffsetPercent: CGPoint, + startRadiusPercent: CGFloat, + endOffsetPercent: CGPoint, + endRadiusPercent: CGFloat + ) { + _type = .radialGradient + _gradient = radialGradient + _gradientStartOffsetPercent = startOffsetPercent + _gradientStartRadiusPercent = startRadiusPercent + _gradientEndOffsetPercent = endOffsetPercent + _gradientEndRadiusPercent = endRadiusPercent + } + + @objc public convenience init(radialGradient: CGGradient) { + self.init( + radialGradient: radialGradient, + startOffsetPercent: CGPoint(x: 0.0, y: 0.0), + startRadiusPercent: 0.0, + endOffsetPercent: CGPoint(x: 0.0, y: 0.0), + endRadiusPercent: 1.0 + ) + } + + @objc public init(CGImage: CGImage, tiled: Bool) { + _type = tiled ? .tiledImage : .image + _image = CGImage + } + + @objc public convenience init(image: NSUIImage, tiled: Bool) { + self.init(CGImage: image.cgImage!, tiled: tiled) + } + + @objc public convenience init(CGImage: CGImage) { + self.init(CGImage: CGImage, tiled: false) + } + + @objc public convenience init(image: NSUIImage) { + self.init(image: image, tiled: false) + } + + @objc public init(CGLayer: CGLayer) { + _type = .layer + _layer = CGLayer + } + + // MARK: Constructors + + @objc open class func fillWithCGColor(_ CGColor: CGColor) -> Fill { + Fill(CGColor: CGColor) + } + + @objc open class func fillWithColor(_ color: NSUIColor) -> Fill { + Fill(color: color) + } + + @objc open class func fillWithLinearGradient( + _ linearGradient: CGGradient, + angle: CGFloat + ) -> Fill { + Fill(linearGradient: linearGradient, angle: angle) + } + + @objc open class func fillWithRadialGradient( + _ radialGradient: CGGradient, + startOffsetPercent: CGPoint, + startRadiusPercent: CGFloat, + endOffsetPercent: CGPoint, + endRadiusPercent: CGFloat + ) -> Fill { + Fill( + radialGradient: radialGradient, + startOffsetPercent: startOffsetPercent, + startRadiusPercent: startRadiusPercent, + endOffsetPercent: endOffsetPercent, + endRadiusPercent: endRadiusPercent + ) + } + + @objc open class func fillWithRadialGradient(_ radialGradient: CGGradient) -> Fill { + Fill(radialGradient: radialGradient) + } + + @objc open class func fillWithCGImage(_ CGImage: CGImage, tiled: Bool) -> Fill { + Fill(CGImage: CGImage, tiled: tiled) + } + + @objc open class func fillWithImage(_ image: NSUIImage, tiled: Bool) -> Fill { + Fill(image: image, tiled: tiled) + } + + @objc open class func fillWithCGImage(_ CGImage: CGImage) -> Fill { + Fill(CGImage: CGImage) + } + + @objc open class func fillWithImage(_ image: NSUIImage) -> Fill { + Fill(image: image) + } + + @objc open class func fillWithCGLayer(_ CGLayer: CGLayer) -> Fill { + Fill(CGLayer: CGLayer) + } + + // MARK: Drawing code + + /// Draws the provided path in filled mode with the provided area + @objc open func fillPath( + context: CGContext, + rect: CGRect + ) { + let fillType = _type + if fillType == .empty { + return + } + + context.saveGState() + + switch fillType { + case .color: + + context.setFillColor(_color!) + context.fillPath() + + case .image: + + context.clip() + context.draw(_image!, in: rect) + + case .tiledImage: + + context.clip() + context.draw(_image!, in: rect, byTiling: true) + + case .layer: + + context.clip() + context.draw(_layer!, in: rect) + + case .linearGradient: + + let radians = (360.0 - _gradientAngle).DEG2RAD + let centerPoint = CGPoint(x: rect.midX, y: rect.midY) + let xAngleDelta = cos(radians) * rect.width / 2.0 + let yAngleDelta = sin(radians) * rect.height / 2.0 + let startPoint = CGPoint( + x: centerPoint.x - xAngleDelta, + y: centerPoint.y - yAngleDelta + ) + let endPoint = CGPoint( + x: centerPoint.x + xAngleDelta, + y: centerPoint.y + yAngleDelta + ) + + context.clip() + context.drawLinearGradient(_gradient!, + start: startPoint, + end: endPoint, + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + + case .radialGradient: + + let centerPoint = CGPoint(x: rect.midX, y: rect.midY) + let radius = max(rect.width, rect.height) / 2.0 + + context.clip() + context.drawRadialGradient(_gradient!, + startCenter: CGPoint( + x: centerPoint.x + rect.width * _gradientStartOffsetPercent.x, + y: centerPoint.y + rect.height * _gradientStartOffsetPercent.y + ), + startRadius: radius * _gradientStartRadiusPercent, + endCenter: CGPoint( + x: centerPoint.x + rect.width * _gradientEndOffsetPercent.x, + y: centerPoint.y + rect.height * _gradientEndOffsetPercent.y + ), + endRadius: radius * _gradientEndRadiusPercent, + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + + case .empty: + break + } + + context.restoreGState() + } +} diff --git a/Pods/Charts/Source/Charts/Utils/Platform+Accessibility.swift b/Pods/Charts/Source/Charts/Utils/Platform+Accessibility.swift new file mode 100644 index 0000000..b801407 --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/Platform+Accessibility.swift @@ -0,0 +1,181 @@ +// +// Platform+Accessibility.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 + +#if os(iOS) || os(tvOS) + #if canImport(UIKit) + import UIKit + #endif + + internal func accessibilityPostLayoutChangedNotification(withElement element: Any? = nil) { + UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: element) + } + + internal func accessibilityPostScreenChangedNotification(withElement element: Any? = nil) { + UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: element) + } + + /// A simple abstraction over UIAccessibilityElement and NSAccessibilityElement. + open class NSUIAccessibilityElement: UIAccessibilityElement { + private weak var containerView: UIView? + + final var isHeader: Bool = false { + didSet { + accessibilityTraits = isHeader ? UIAccessibilityTraits.header : UIAccessibilityTraits.none + } + } + + final var isSelected: Bool = false { + didSet { + accessibilityTraits = isSelected ? UIAccessibilityTraits.selected : UIAccessibilityTraits.none + } + } + + override public init(accessibilityContainer container: Any) { + // We can force unwrap since all chart views are subclasses of UIView + containerView = (container as! UIView) + super.init(accessibilityContainer: container) + } + + override open var accessibilityFrame: CGRect { + get { + super.accessibilityFrame + } + + set { + guard let containerView = containerView else { return } + super.accessibilityFrame = containerView.convert(newValue, to: UIScreen.main.coordinateSpace) + } + } + } + + extension NSUIView { + /// An array of accessibilityElements that is used to implement UIAccessibilityContainer internally. + /// Subclasses **MUST** override this with an array of such elements. + @objc open func accessibilityChildren() -> [Any]? { + nil + } + + override public final var isAccessibilityElement: Bool { + get { false } // Return false here, so we can make individual elements accessible + set {} + } + + override open func accessibilityElementCount() -> Int { + accessibilityChildren()?.count ?? 0 + } + + override open func accessibilityElement(at index: Int) -> Any? { + accessibilityChildren()?[index] + } + + override open func index(ofAccessibilityElement element: Any) -> Int { + guard let axElement = element as? NSUIAccessibilityElement else { return NSNotFound } + return (accessibilityChildren() as? [NSUIAccessibilityElement])? + .firstIndex(of: axElement) ?? NSNotFound + } + } + +#endif + +#if os(OSX) + + #if canImport(AppKit) + import AppKit + #endif + + internal func accessibilityPostLayoutChangedNotification(withElement element: Any? = nil) { + guard let validElement = element else { return } + NSAccessibility.post(element: validElement, notification: .layoutChanged) + } + + internal func accessibilityPostScreenChangedNotification(withElement _: Any? = nil) { + // Placeholder + } + + /// A simple abstraction over UIAccessibilityElement and NSAccessibilityElement. + open class NSUIAccessibilityElement: NSAccessibilityElement { + private weak var containerView: NSView? + + final var isHeader: Bool = false { + didSet { + setAccessibilityRole(isHeader ? .staticText : .none) + } + } + + final var isSelected: Bool = false { + didSet { + setAccessibilitySelected(isSelected) + } + } + + open var accessibilityLabel: String { + get { + accessibilityLabel() ?? "" + } + + set { + setAccessibilityLabel(newValue) + } + } + + open var accessibilityFrame: NSRect { + get { + accessibilityFrame() + } + + set { + guard let containerView = containerView else { return } + + let bounds = NSAccessibility.screenRect(fromView: containerView, rect: newValue) + + // This works, but won't auto update if the window is resized or moved. + // setAccessibilityFrame(bounds) + + // using FrameInParentSpace allows for automatic updating of frame when windows are moved and resized. + // However, there seems to be a bug right now where using it causes an offset in the frame. + // This is a slightly hacky workaround that calculates the offset and removes it from frame calculation. + setAccessibilityFrameInParentSpace(bounds) + let axFrame = accessibilityFrame() + let widthOffset = abs(axFrame.origin.x - bounds.origin.x) + let heightOffset = abs(axFrame.origin.y - bounds.origin.y) + let rect = NSRect(x: bounds.origin.x - widthOffset, + y: bounds.origin.y - heightOffset, + width: bounds.width, + height: bounds.height) + setAccessibilityFrameInParentSpace(rect) + } + } + + public init(accessibilityContainer container: Any) { + // We can force unwrap since all chart views are subclasses of NSView + containerView = (container as! NSView) + + super.init() + + setAccessibilityParent(containerView) + setAccessibilityRole(.row) + } + } + + /// - Note: setAccessibilityRole(.list) is called at init. See Platform.swift. + extension NSUIView: NSAccessibilityGroup { + override open func accessibilityLabel() -> String? { + "Chart View" + } + + override open func accessibilityRows() -> [Any]? { + accessibilityChildren() + } + } + +#endif diff --git a/Pods/Charts/Source/Charts/Utils/Platform+Color.swift b/Pods/Charts/Source/Charts/Utils/Platform+Color.swift new file mode 100644 index 0000000..7a98afa --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/Platform+Color.swift @@ -0,0 +1,49 @@ +// +// Platform+Color.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 +// + +#if canImport(UIKit) + import UIKit + + public typealias NSUIColor = UIColor + private func fetchLabelColor() -> UIColor { + if #available(iOS 13, tvOS 13, *) { + return .label + } else { + return .black + } + } + + private let labelColor: UIColor = fetchLabelColor() + + extension UIColor { + static var labelOrBlack: UIColor { labelColor } + } +#endif + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) + + import AppKit + + public typealias NSUIColor = NSColor + private func fetchLabelColor() -> NSColor { + if #available(macOS 10.14, *) { + return .labelColor + } else { + return .black + } + } + + private let labelColor: NSColor = fetchLabelColor() + + extension NSColor { + static var labelOrBlack: NSColor { labelColor } + } +#endif diff --git a/Pods/Charts/Source/Charts/Utils/Platform+Gestures.swift b/Pods/Charts/Source/Charts/Utils/Platform+Gestures.swift new file mode 100644 index 0000000..00bb078 --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/Platform+Gestures.swift @@ -0,0 +1,143 @@ +// +// Platform+Gestures.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 +// + +// MARK: - UIKit + +#if canImport(UIKit) + import UIKit + + public typealias NSUIGestureRecognizer = UIGestureRecognizer + public typealias NSUIGestureRecognizerState = UIGestureRecognizer.State + public typealias NSUIGestureRecognizerDelegate = UIGestureRecognizerDelegate + public typealias NSUITapGestureRecognizer = UITapGestureRecognizer + public typealias NSUIPanGestureRecognizer = UIPanGestureRecognizer + + extension NSUITapGestureRecognizer { + @objc final func nsuiNumberOfTouches() -> Int { + numberOfTouches + } + + @objc final var nsuiNumberOfTapsRequired: Int { + get { + numberOfTapsRequired + } + set { + numberOfTapsRequired = newValue + } + } + } + + extension NSUIPanGestureRecognizer { + @objc final func nsuiNumberOfTouches() -> Int { + numberOfTouches + } + + @objc final func nsuiLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint { + super.location(ofTouch: touch, in: inView) + } + } + + #if !os(tvOS) + public typealias NSUIPinchGestureRecognizer = UIPinchGestureRecognizer + public typealias NSUIRotationGestureRecognizer = UIRotationGestureRecognizer + + extension NSUIRotationGestureRecognizer { + @objc final var nsuiRotation: CGFloat { + get { rotation } + set { rotation = newValue } + } + } + + extension NSUIPinchGestureRecognizer { + @objc final var nsuiScale: CGFloat { + get { + scale + } + set { + scale = newValue + } + } + + @objc final func nsuiLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint { + super.location(ofTouch: touch, in: inView) + } + } + #endif +#endif + +// MARK: - AppKit + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) + import AppKit + + public typealias NSUIGestureRecognizer = NSGestureRecognizer + public typealias NSUIGestureRecognizerState = NSGestureRecognizer.State + public typealias NSUIGestureRecognizerDelegate = NSGestureRecognizerDelegate + public typealias NSUITapGestureRecognizer = NSClickGestureRecognizer + public typealias NSUIPanGestureRecognizer = NSPanGestureRecognizer + public typealias NSUIPinchGestureRecognizer = NSMagnificationGestureRecognizer + public typealias NSUIRotationGestureRecognizer = NSRotationGestureRecognizer + + /** The 'tap' gesture is mapped to clicks. */ + extension NSUITapGestureRecognizer { + final func nsuiNumberOfTouches() -> Int { + 1 + } + + final var nsuiNumberOfTapsRequired: Int { + get { + numberOfClicksRequired + } + set { + numberOfClicksRequired = newValue + } + } + } + + extension NSUIPanGestureRecognizer { + final func nsuiNumberOfTouches() -> Int { + 1 + } + + // FIXME: Currently there are no more than 1 touch in OSX gestures, and not way to create custom touch gestures. + final func nsuiLocationOfTouch(_: Int, inView: NSView?) -> NSPoint { + super.location(in: inView) + } + } + + extension NSUIRotationGestureRecognizer { + // FIXME: Currently there are no velocities in OSX gestures, and not way to create custom touch gestures. + final var velocity: CGFloat { + 0.1 + } + + final var nsuiRotation: CGFloat { + get { -rotation } + set { rotation = -newValue } + } + } + + extension NSUIPinchGestureRecognizer { + final var nsuiScale: CGFloat { + get { + magnification + 1.0 + } + set { + magnification = newValue - 1.0 + } + } + + // FIXME: Currently there are no more than 1 touch in OSX gestures, and not way to create custom touch gestures. + final func nsuiLocationOfTouch(_: Int, inView view: NSView?) -> NSPoint { + super.location(in: view) + } + } +#endif diff --git a/Pods/Charts/Source/Charts/Utils/Platform+Graphics.swift b/Pods/Charts/Source/Charts/Utils/Platform+Graphics.swift new file mode 100644 index 0000000..054229d --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/Platform+Graphics.swift @@ -0,0 +1,141 @@ +// +// Platform+Graphics.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 +// + +enum Orientation { + case portrait, landscape +} + +extension CGSize { + var orientation: Orientation { width > height ? .landscape : .portrait } +} + +extension CGRect { + var orientation: Orientation { size.orientation } +} + +// MARK: - UIKit + +#if canImport(UIKit) + import UIKit + + func NSUIGraphicsGetCurrentContext() -> CGContext? { + UIGraphicsGetCurrentContext() + } + + func NSUIGraphicsGetImageFromCurrentImageContext() -> NSUIImage! { + UIGraphicsGetImageFromCurrentImageContext() + } + + func NSUIGraphicsPushContext(_ context: CGContext) { + UIGraphicsPushContext(context) + } + + func NSUIGraphicsPopContext() { + UIGraphicsPopContext() + } + + func NSUIGraphicsEndImageContext() { + UIGraphicsEndImageContext() + } + + func NSUIImagePNGRepresentation(_ image: NSUIImage) -> Data? { + image.pngData() + } + + func NSUIImageJPEGRepresentation(_ image: NSUIImage, _ quality: CGFloat = 0.8) -> Data? { + image.jpegData(compressionQuality: quality) + } + + func NSUIGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) { + UIGraphicsBeginImageContextWithOptions(size, opaque, scale) + } +#endif + +// MARK: - AppKit + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) + import AppKit + + func NSUIGraphicsGetCurrentContext() -> CGContext? { + NSGraphicsContext.current?.cgContext + } + + func NSUIGraphicsPushContext(_ context: CGContext) { + let cx = NSGraphicsContext(cgContext: context, flipped: true) + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = cx + } + + func NSUIGraphicsPopContext() { + NSGraphicsContext.restoreGraphicsState() + } + + func NSUIImagePNGRepresentation(_ image: NSUIImage) -> Data? { + image.lockFocus() + let rep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height)) + image.unlockFocus() + return rep?.representation(using: .png, properties: [:]) + } + + func NSUIImageJPEGRepresentation(_ image: NSUIImage, _ quality: CGFloat = 0.9) -> Data? { + image.lockFocus() + let rep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height)) + image.unlockFocus() + return rep?.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: quality]) + } + + private var imageContextStack: [CGFloat] = [] + + func NSUIGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) { + var scale = scale + if scale == 0.0 { + scale = NSScreen.main?.backingScaleFactor ?? 1.0 + } + + let width = Int(size.width * scale) + let height = Int(size.height * scale) + + if width > 0, height > 0 { + imageContextStack.append(scale) + + let colorSpace = CGColorSpaceCreateDeviceRGB() + + guard let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 4 * width, space: colorSpace, bitmapInfo: opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue) + else { return } + + ctx.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(height))) + ctx.scaleBy(x: scale, y: scale) + NSUIGraphicsPushContext(ctx) + } + } + + func NSUIGraphicsGetImageFromCurrentImageContext() -> NSUIImage? { + if !imageContextStack.isEmpty { + guard let ctx = NSUIGraphicsGetCurrentContext() + else { return nil } + + let scale = imageContextStack.last! + if let theCGImage = ctx.makeImage() { + let size = CGSize(width: CGFloat(ctx.width) / scale, height: CGFloat(ctx.height) / scale) + let image = NSImage(cgImage: theCGImage, size: size) + return image + } + } + return nil + } + + func NSUIGraphicsEndImageContext() { + if imageContextStack.last != nil { + imageContextStack.removeLast() + NSUIGraphicsPopContext() + } + } +#endif diff --git a/Pods/Charts/Source/Charts/Utils/Platform+Touch Handling.swift b/Pods/Charts/Source/Charts/Utils/Platform+Touch Handling.swift new file mode 100644 index 0000000..fcd0c1c --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/Platform+Touch Handling.swift @@ -0,0 +1,112 @@ +// +// Platform+Touch Handling.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 +// + +#if canImport(UIKit) + import UIKit + + public typealias NSUIEvent = UIEvent + public typealias NSUITouch = UITouch + + @objc + extension NSUIView { + override public final func touchesBegan(_ touches: Set, with event: NSUIEvent?) { + nsuiTouchesBegan(touches, withEvent: event) + } + + override public final func touchesMoved(_ touches: Set, with event: NSUIEvent?) { + nsuiTouchesMoved(touches, withEvent: event) + } + + override public final func touchesEnded(_ touches: Set, with event: NSUIEvent?) { + nsuiTouchesEnded(touches, withEvent: event) + } + + override public final func touchesCancelled(_ touches: Set, with event: NSUIEvent?) { + nsuiTouchesCancelled(touches, withEvent: event) + } + + open func nsuiTouchesBegan(_ touches: Set, withEvent event: NSUIEvent?) { + super.touchesBegan(touches, with: event!) + } + + open func nsuiTouchesMoved(_ touches: Set, withEvent event: NSUIEvent?) { + super.touchesMoved(touches, with: event!) + } + + open func nsuiTouchesEnded(_ touches: Set, withEvent event: NSUIEvent?) { + super.touchesEnded(touches, with: event!) + } + + open func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) { + super.touchesCancelled(touches!, with: event!) + } + } + + extension UIView { + @objc final var nsuiGestureRecognizers: [NSUIGestureRecognizer]? { + gestureRecognizers + } + } +#endif + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) + import AppKit + + public typealias NSUIEvent = NSEvent + public typealias NSUITouch = NSTouch + + @objc + extension NSUIView { + override public final func touchesBegan(with event: NSEvent) { + nsuiTouchesBegan(event.touches(matching: .any, in: self), withEvent: event) + } + + override public final func touchesEnded(with event: NSEvent) { + nsuiTouchesEnded(event.touches(matching: .any, in: self), withEvent: event) + } + + override public final func touchesMoved(with event: NSEvent) { + nsuiTouchesMoved(event.touches(matching: .any, in: self), withEvent: event) + } + + override open func touchesCancelled(with event: NSEvent) { + nsuiTouchesCancelled(event.touches(matching: .any, in: self), withEvent: event) + } + + open func nsuiTouchesBegan(_: Set, withEvent event: NSUIEvent?) { + super.touchesBegan(with: event!) + } + + open func nsuiTouchesMoved(_: Set, withEvent event: NSUIEvent?) { + super.touchesMoved(with: event!) + } + + open func nsuiTouchesEnded(_: Set, withEvent event: NSUIEvent?) { + super.touchesEnded(with: event!) + } + + open func nsuiTouchesCancelled(_: Set?, withEvent event: NSUIEvent?) { + super.touchesCancelled(with: event!) + } + } + + extension NSTouch { + /** Touch locations on OS X are relative to the trackpad, whereas on iOS they are actually *on* the view. */ + func locationInView(view: NSView) -> NSPoint { + let n = normalizedPosition + let b = view.bounds + return NSPoint( + x: b.origin.x + b.size.width * n.x, + y: b.origin.y + b.size.height * n.y + ) + } + } +#endif diff --git a/Pods/Charts/Source/Charts/Utils/Platform.swift b/Pods/Charts/Source/Charts/Utils/Platform.swift new file mode 100644 index 0000000..3aa7b59 --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/Platform.swift @@ -0,0 +1,192 @@ +import Foundation + +/** This file provides a thin abstraction layer atop of UIKit (iOS, tvOS) and Cocoa (OS X). The two APIs are very much + alike, and for the chart library's usage of the APIs it is often sufficient to typealias one to the other. The NSUI* + types are aliased to either their UI* implementation (on iOS) or their NS* implementation (on OS X). */ +#if os(iOS) || os(tvOS) + #if canImport(UIKit) + import UIKit + #endif + + public typealias NSUIFont = UIFont + public typealias NSUIImage = UIImage + public typealias NSUIScrollView = UIScrollView + public typealias NSUIScreen = UIScreen + public typealias NSUIDisplayLink = CADisplayLink + + open class NSUIView: UIView { + @objc var nsuiLayer: CALayer? { + layer + } + } + + extension UIScrollView { + @objc var nsuiIsScrollEnabled: Bool { + get { isScrollEnabled } + set { isScrollEnabled = newValue } + } + } + + extension UIScreen { + @objc final var nsuiScale: CGFloat { + scale + } + } + +#endif + +#if os(OSX) + import Cocoa + import Quartz + + public typealias NSUIFont = NSFont + public typealias NSUIImage = NSImage + public typealias NSUIScrollView = NSScrollView + public typealias NSUIScreen = NSScreen + + /** On OS X there is no CADisplayLink. Use a 60 fps timer to render the animations. */ + public class NSUIDisplayLink { + private var timer: Timer? + private var displayLink: CVDisplayLink? + private var _timestamp: CFTimeInterval = 0.0 + + private weak var _target: AnyObject? + private var _selector: Selector + + public var timestamp: CFTimeInterval { + _timestamp + } + + init(target: Any, selector: Selector) { + _target = target as AnyObject + _selector = selector + + if CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) == kCVReturnSuccess { + CVDisplayLinkSetOutputCallback(displayLink!, { (_, _, _, _, _, userData) -> CVReturn in + + let _self = unsafeBitCast(userData, to: NSUIDisplayLink.self) + + _self._timestamp = CFAbsoluteTimeGetCurrent() + _self._target?.performSelector(onMainThread: _self._selector, with: _self, waitUntilDone: false) + + return kCVReturnSuccess + }, Unmanaged.passUnretained(self).toOpaque()) + } else { + timer = Timer(timeInterval: 1.0 / 60.0, target: target, selector: selector, userInfo: nil, repeats: true) + } + } + + deinit { + stop() + } + + open func add(to runloop: RunLoop, forMode mode: RunLoop.Mode) { + if displayLink != nil { + CVDisplayLinkStart(displayLink!) + } else if timer != nil { + runloop.add(timer!, forMode: mode) + } + } + + open func remove(from _: RunLoop, forMode _: RunLoop.Mode) { + stop() + } + + private func stop() { + if displayLink != nil { + CVDisplayLinkStop(displayLink!) + } + if timer != nil { + timer?.invalidate() + } + } + } + + extension NSView { + final var nsuiGestureRecognizers: [NSGestureRecognizer]? { + gestureRecognizers + } + } + + extension NSScrollView { + var nsuiIsScrollEnabled: Bool { + get { scrollEnabled } + set { scrollEnabled = newValue } + } + } + + open class NSUIView: NSView { + /// A private constant to set the accessibility role during initialization. + /// It ensures parity with the iOS element ordering as well as numbered counts of chart components. + /// (See Platform+Accessibility for details) + private let role: NSAccessibility.Role = .list + + override public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + setAccessibilityRole(role) + } + + public required init?(coder decoder: NSCoder) { + super.init(coder: decoder) + setAccessibilityRole(role) + } + + override public final var isFlipped: Bool { + true + } + + func setNeedsDisplay() { + setNeedsDisplay(bounds) + } + + open var backgroundColor: NSUIColor? { + get { + self.layer?.backgroundColor == nil + ? nil + : NSColor(cgColor: self.layer!.backgroundColor!) + } + set { + self.wantsLayer = true + self.layer?.backgroundColor = newValue == nil ? nil : newValue!.cgColor + } + } + + final var nsuiLayer: CALayer? { + layer + } + } + + extension NSFont { + var lineHeight: CGFloat { + // Not sure if this is right, but it looks okay + boundingRectForFont.size.height + } + } + + extension NSScreen { + final var nsuiScale: CGFloat { + backingScaleFactor + } + } + + extension NSImage { + var cgImage: CGImage? { + self.cgImage(forProposedRect: nil, context: nil, hints: nil) + } + } + + extension NSScrollView { + /// NOTE: Unable to disable scrolling in macOS + var scrollEnabled: Bool { + get { + true + } + set {} + } + } + +#endif + +extension NSUIScreen { + class var nsuiMain: NSUIScreen? { .main } +} diff --git a/Pods/Charts/Source/Charts/Utils/Transformer.swift b/Pods/Charts/Source/Charts/Utils/Transformer.swift new file mode 100644 index 0000000..8e30724 --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/Transformer.swift @@ -0,0 +1,146 @@ +// +// Transformer.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 CoreGraphics +import Foundation + +/// Transformer class that contains all matrices and is responsible for transforming values into pixels on the screen and backwards. +@objc(ChartTransformer) +open class Transformer: NSObject { + /// matrix to map the values to the screen pixels + internal var _matrixValueToPx = CGAffineTransform.identity + + /// matrix for handling the different offsets of the chart + internal var _matrixOffset = CGAffineTransform.identity + + internal var _viewPortHandler: ViewPortHandler + + @objc public init(viewPortHandler: ViewPortHandler) { + _viewPortHandler = viewPortHandler + } + + /// Prepares the matrix that transforms values to pixels. Calculates the scale factors from the charts size and offsets. + @objc open func prepareMatrixValuePx(chartXMin: Double, deltaX: CGFloat, deltaY: CGFloat, chartYMin: Double) { + var scaleX = (_viewPortHandler.contentWidth / deltaX) + var scaleY = (_viewPortHandler.contentHeight / deltaY) + + if CGFloat.infinity == scaleX { + scaleX = 0.0 + } + if CGFloat.infinity == scaleY { + scaleY = 0.0 + } + + // setup all matrices + _matrixValueToPx = CGAffineTransform.identity + _matrixValueToPx = _matrixValueToPx.scaledBy(x: scaleX, y: -scaleY) + _matrixValueToPx = _matrixValueToPx.translatedBy(x: CGFloat(-chartXMin), y: CGFloat(-chartYMin)) + } + + /// Prepares the matrix that contains all offsets. + @objc open func prepareMatrixOffset(inverted: Bool) { + if !inverted { + _matrixOffset = CGAffineTransform(translationX: _viewPortHandler.offsetLeft, y: _viewPortHandler.chartHeight - _viewPortHandler.offsetBottom) + } else { + _matrixOffset = CGAffineTransform(scaleX: 1.0, y: -1.0) + _matrixOffset = _matrixOffset.translatedBy(x: _viewPortHandler.offsetLeft, y: -_viewPortHandler.offsetTop) + } + } + + /// Transform an array of points with all matrices. + // VERY IMPORTANT: Keep matrix order "value-touch-offset" when transforming. + open func pointValuesToPixel(_ points: inout [CGPoint]) { + let trans = valueToPixelMatrix + points = points.map { $0.applying(trans) } + } + + open func pointValueToPixel(_ point: inout CGPoint) { + point = point.applying(valueToPixelMatrix) + } + + @objc open func pixelForValues(x: Double, y: Double) -> CGPoint { + CGPoint(x: x, y: y).applying(valueToPixelMatrix) + } + + /// Transform a rectangle with all matrices. + open func rectValueToPixel(_ r: inout CGRect) { + r = r.applying(valueToPixelMatrix) + } + + /// Transform a rectangle with all matrices with potential animation phases. + open func rectValueToPixel(_ r: inout CGRect, phaseY: Double) { + // multiply the height of the rect with the phase + var bottom = r.origin.y + r.size.height + bottom *= CGFloat(phaseY) + let top = r.origin.y * CGFloat(phaseY) + r.size.height = bottom - top + r.origin.y = top + + r = r.applying(valueToPixelMatrix) + } + + /// Transform a rectangle with all matrices. + open func rectValueToPixelHorizontal(_ r: inout CGRect) { + r = r.applying(valueToPixelMatrix) + } + + /// Transform a rectangle with all matrices with potential animation phases. + open func rectValueToPixelHorizontal(_ r: inout CGRect, phaseY: Double) { + // multiply the height of the rect with the phase + let left = r.origin.x * CGFloat(phaseY) + let right = (r.origin.x + r.size.width) * CGFloat(phaseY) + r.size.width = right - left + r.origin.x = left + + r = r.applying(valueToPixelMatrix) + } + + /// transforms multiple rects with all matrices + open func rectValuesToPixel(_ rects: inout [CGRect]) { + let trans = valueToPixelMatrix + rects = rects.map { $0.applying(trans) } + } + + /// Transforms the given array of touch points (pixels) into values on the chart. + open func pixelsToValues(_ pixels: inout [CGPoint]) { + let trans = pixelToValueMatrix + pixels = pixels.map { $0.applying(trans) } + } + + /// Transforms the given touch point (pixels) into a value on the chart. + open func pixelToValues(_ pixel: inout CGPoint) { + pixel = pixel.applying(pixelToValueMatrix) + } + + /// - Returns: The x and y values in the chart at the given touch point + /// (encapsulated in a CGPoint). This method transforms pixel coordinates to + /// coordinates / values in the chart. + @objc open func valueForTouchPoint(_ point: CGPoint) -> CGPoint { + point.applying(pixelToValueMatrix) + } + + /// - Returns: The x and y values in the chart at the given touch point + /// (x/y). This method transforms pixel coordinates to + /// coordinates / values in the chart. + @objc open func valueForTouchPoint(x: CGFloat, y: CGFloat) -> CGPoint { + CGPoint(x: x, y: y).applying(pixelToValueMatrix) + } + + @objc open var valueToPixelMatrix: CGAffineTransform { + _matrixValueToPx.concatenating(_viewPortHandler.touchMatrix + ).concatenating(_matrixOffset + ) + } + + @objc open var pixelToValueMatrix: CGAffineTransform { + valueToPixelMatrix.inverted() + } +} diff --git a/Pods/Charts/Source/Charts/Utils/TransformerHorizontalBarChart.swift b/Pods/Charts/Source/Charts/Utils/TransformerHorizontalBarChart.swift new file mode 100644 index 0000000..5bfd335 --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/TransformerHorizontalBarChart.swift @@ -0,0 +1,27 @@ +// +// TransformerHorizontalBarChart.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 CoreGraphics +import Foundation + +@objc(ChartTransformerHorizontalBarChart) +open class TransformerHorizontalBarChart: Transformer { + /// Prepares the matrix that contains all offsets. + override open func prepareMatrixOffset(inverted: Bool) { + if !inverted { + _matrixOffset = CGAffineTransform(translationX: _viewPortHandler.offsetLeft, y: _viewPortHandler.chartHeight - _viewPortHandler.offsetBottom) + } else { + _matrixOffset = CGAffineTransform(scaleX: -1.0, y: 1.0) + _matrixOffset = _matrixOffset.translatedBy(x: -(_viewPortHandler.chartWidth - _viewPortHandler.offsetRight), + y: _viewPortHandler.chartHeight - _viewPortHandler.offsetBottom) + } + } +} diff --git a/Pods/Charts/Source/Charts/Utils/ViewPortHandler.swift b/Pods/Charts/Source/Charts/Utils/ViewPortHandler.swift new file mode 100755 index 0000000..7a20be4 --- /dev/null +++ b/Pods/Charts/Source/Charts/Utils/ViewPortHandler.swift @@ -0,0 +1,532 @@ +// +// ViewPortHandler.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 CoreGraphics +import Foundation + +/// Class that contains information about the charts current viewport settings, including offsets, scale & translation levels, ... +@objc(ChartViewPortHandler) +open class ViewPortHandler: NSObject { + /// matrix used for touch events + private var _touchMatrix = CGAffineTransform.identity + + /// this rectangle defines the area in which graph values can be drawn + private var _contentRect = CGRect() + + private var _chartWidth = CGFloat(0.0) + private var _chartHeight = CGFloat(0.0) + + /// minimum scale value on the y-axis + private var _minScaleY = CGFloat(1.0) + + /// maximum scale value on the y-axis + private var _maxScaleY = CGFloat.greatestFiniteMagnitude + + /// minimum scale value on the x-axis + private var _minScaleX = CGFloat(1.0) + + /// maximum scale value on the x-axis + private var _maxScaleX = CGFloat.greatestFiniteMagnitude + + /// contains the current scale factor of the x-axis + private var _scaleX = CGFloat(1.0) + + /// contains the current scale factor of the y-axis + private var _scaleY = CGFloat(1.0) + + /// current translation (drag distance) on the x-axis + private var _transX = CGFloat(0.0) + + /// current translation (drag distance) on the y-axis + private var _transY = CGFloat(0.0) + + /// offset that allows the chart to be dragged over its bounds on the x-axis + private var _transOffsetX = CGFloat(0.0) + + /// offset that allows the chart to be dragged over its bounds on the x-axis + private var _transOffsetY = CGFloat(0.0) + + /// Constructor - don't forget calling setChartDimens(...) + @objc public init(width: CGFloat, height: CGFloat) { + super.init() + + setChartDimens(width: width, height: height) + } + + @objc open func setChartDimens(width: CGFloat, height: CGFloat) { + let offsetLeft = self.offsetLeft + let offsetTop = self.offsetTop + let offsetRight = self.offsetRight + let offsetBottom = self.offsetBottom + + _chartHeight = height + _chartWidth = width + + restrainViewPort(offsetLeft: offsetLeft, offsetTop: offsetTop, offsetRight: offsetRight, offsetBottom: offsetBottom) + } + + @objc open var hasChartDimens: Bool { + if _chartHeight > 0.0, _chartWidth > 0.0 { + return true + } else { + return false + } + } + + @objc open func restrainViewPort(offsetLeft: CGFloat, offsetTop: CGFloat, offsetRight: CGFloat, offsetBottom: CGFloat) { + _contentRect.origin.x = offsetLeft + _contentRect.origin.y = offsetTop + _contentRect.size.width = _chartWidth - offsetLeft - offsetRight + _contentRect.size.height = _chartHeight - offsetBottom - offsetTop + } + + @objc open var offsetLeft: CGFloat { + _contentRect.origin.x + } + + @objc open var offsetRight: CGFloat { + _chartWidth - _contentRect.size.width - _contentRect.origin.x + } + + @objc open var offsetTop: CGFloat { + _contentRect.origin.y + } + + @objc open var offsetBottom: CGFloat { + _chartHeight - _contentRect.size.height - _contentRect.origin.y + } + + @objc open var contentTop: CGFloat { + _contentRect.origin.y + } + + @objc open var contentLeft: CGFloat { + _contentRect.origin.x + } + + @objc open var contentRight: CGFloat { + _contentRect.origin.x + _contentRect.size.width + } + + @objc open var contentBottom: CGFloat { + _contentRect.origin.y + _contentRect.size.height + } + + @objc open var contentWidth: CGFloat { + _contentRect.size.width + } + + @objc open var contentHeight: CGFloat { + _contentRect.size.height + } + + @objc open var contentRect: CGRect { + _contentRect + } + + @objc open var contentCenter: CGPoint { + CGPoint(x: _contentRect.origin.x + _contentRect.size.width / 2.0, y: _contentRect.origin.y + _contentRect.size.height / 2.0) + } + + @objc open var chartHeight: CGFloat { + _chartHeight + } + + @objc open var chartWidth: CGFloat { + _chartWidth + } + + // MARK: - Scaling/Panning etc. + + /// Zooms by the specified zoom factors. + @objc open func zoom(scaleX: CGFloat, scaleY: CGFloat) -> CGAffineTransform { + _touchMatrix.scaledBy(x: scaleX, y: scaleY) + } + + /// Zooms around the specified center + @objc open func zoom(scaleX: CGFloat, scaleY: CGFloat, x: CGFloat, y: CGFloat) -> CGAffineTransform { + var matrix = _touchMatrix.translatedBy(x: x, y: y) + matrix = matrix.scaledBy(x: scaleX, y: scaleY) + matrix = matrix.translatedBy(x: -x, y: -y) + return matrix + } + + /// Zooms in by 1.4, x and y are the coordinates (in pixels) of the zoom center. + @objc open func zoomIn(x: CGFloat, y: CGFloat) -> CGAffineTransform { + zoom(scaleX: 1.4, scaleY: 1.4, x: x, y: y) + } + + /// Zooms out by 0.7, x and y are the coordinates (in pixels) of the zoom center. + @objc open func zoomOut(x: CGFloat, y: CGFloat) -> CGAffineTransform { + zoom(scaleX: 0.7, scaleY: 0.7, x: x, y: y) + } + + /// Zooms out to original size. + @objc open func resetZoom() -> CGAffineTransform { + zoom(scaleX: 1.0, scaleY: 1.0, x: 0.0, y: 0.0) + } + + /// Sets the scale factor to the specified values. + @objc open func setZoom(scaleX: CGFloat, scaleY: CGFloat) -> CGAffineTransform { + var matrix = _touchMatrix + matrix.a = scaleX + matrix.d = scaleY + return matrix + } + + /// Sets the scale factor to the specified values. x and y is pivot. + @objc open func setZoom(scaleX: CGFloat, scaleY: CGFloat, x: CGFloat, y: CGFloat) -> CGAffineTransform { + var matrix = _touchMatrix + matrix.a = 1.0 + matrix.d = 1.0 + matrix = matrix.translatedBy(x: x, y: y) + matrix = matrix.scaledBy(x: scaleX, y: scaleY) + matrix = matrix.translatedBy(x: -x, y: -y) + return matrix + } + + /// Resets all zooming and dragging and makes the chart fit exactly it's bounds. + @objc open func fitScreen() -> CGAffineTransform { + _minScaleX = 1.0 + _minScaleY = 1.0 + + return CGAffineTransform.identity + } + + /// Translates to the specified point. + @objc open func translate(pt: CGPoint) -> CGAffineTransform { + let translateX = pt.x - offsetLeft + let translateY = pt.y - offsetTop + + let matrix = _touchMatrix.concatenating(CGAffineTransform(translationX: -translateX, y: -translateY)) + + return matrix + } + + /// Centers the viewport around the specified position (x-index and y-value) in the chart. + /// Centering the viewport outside the bounds of the chart is not possible. + /// Makes most sense in combination with the setScaleMinima(...) method. + @objc open func centerViewPort(pt: CGPoint, chart: ChartViewBase) { + let translateX = pt.x - offsetLeft + let translateY = pt.y - offsetTop + + let matrix = _touchMatrix.concatenating(CGAffineTransform(translationX: -translateX, y: -translateY)) + refresh(newMatrix: matrix, chart: chart, invalidate: true) + } + + /// call this method to refresh the graph with a given matrix + @objc @discardableResult open func refresh(newMatrix: CGAffineTransform, chart: ChartViewBase, invalidate _: Bool) -> CGAffineTransform { + _touchMatrix = newMatrix + + // make sure scale and translation are within their bounds + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + + chart.setNeedsDisplay() + + return _touchMatrix + } + + /// limits the maximum scale and X translation of the given matrix + private func limitTransAndScale(matrix: inout CGAffineTransform, content: CGRect?) { + // min scale-x is 1 + _scaleX = min(max(_minScaleX, matrix.a), _maxScaleX) + + // min scale-y is 1 + _scaleY = min(max(_minScaleY, matrix.d), _maxScaleY) + + var width: CGFloat = 0.0 + var height: CGFloat = 0.0 + + if content != nil { + width = content!.width + height = content!.height + } + + let maxTransX = -width * (_scaleX - 1.0) + _transX = min(max(matrix.tx, maxTransX - _transOffsetX), _transOffsetX) + + let maxTransY = height * (_scaleY - 1.0) + _transY = max(min(matrix.ty, maxTransY + _transOffsetY), -_transOffsetY) + + matrix.tx = _transX + matrix.a = _scaleX + matrix.ty = _transY + matrix.d = _scaleY + } + + /// Sets the minimum scale factor for the x-axis + @objc open func setMinimumScaleX(_ xScale: CGFloat) { + var newValue = xScale + + if newValue < 1.0 { + newValue = 1.0 + } + + _minScaleX = newValue + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + /// Sets the maximum scale factor for the x-axis + @objc open func setMaximumScaleX(_ xScale: CGFloat) { + var newValue = xScale + + if newValue == 0.0 { + newValue = CGFloat.greatestFiniteMagnitude + } + + _maxScaleX = newValue + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + /// Sets the minimum and maximum scale factors for the x-axis + @objc open func setMinMaxScaleX(minScaleX: CGFloat, maxScaleX: CGFloat) { + var newMin = minScaleX + var newMax = maxScaleX + + if newMin < 1.0 { + newMin = 1.0 + } + if newMax == 0.0 { + newMax = CGFloat.greatestFiniteMagnitude + } + + _minScaleX = newMin + _maxScaleX = maxScaleX + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + /// Sets the minimum scale factor for the y-axis + @objc open func setMinimumScaleY(_ yScale: CGFloat) { + var newValue = yScale + + if newValue < 1.0 { + newValue = 1.0 + } + + _minScaleY = newValue + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + /// Sets the maximum scale factor for the y-axis + @objc open func setMaximumScaleY(_ yScale: CGFloat) { + var newValue = yScale + + if newValue == 0.0 { + newValue = CGFloat.greatestFiniteMagnitude + } + + _maxScaleY = newValue + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + @objc open func setMinMaxScaleY(minScaleY: CGFloat, maxScaleY: CGFloat) { + var minScaleY = minScaleY, maxScaleY = maxScaleY + + if minScaleY < 1.0 { + minScaleY = 1.0 + } + + if maxScaleY == 0.0 { + maxScaleY = CGFloat.greatestFiniteMagnitude + } + + _minScaleY = minScaleY + _maxScaleY = maxScaleY + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + @objc open var touchMatrix: CGAffineTransform { + _touchMatrix + } + + // MARK: - Boundaries Check + + @objc open func isInBoundsX(_ x: CGFloat) -> Bool { + isInBoundsLeft(x) && isInBoundsRight(x) + } + + @objc open func isInBoundsY(_ y: CGFloat) -> Bool { + isInBoundsTop(y) && isInBoundsBottom(y) + } + + /** + A method to check whether coordinate lies within the viewport. + + - Parameters: + - point: a coordinate. + */ + @objc open func isInBounds(point: CGPoint) -> Bool { + isInBounds(x: point.x, y: point.y) + } + + @objc open func isInBounds(x: CGFloat, y: CGFloat) -> Bool { + isInBoundsX(x) && isInBoundsY(y) + } + + @objc open func isInBoundsLeft(_ x: CGFloat) -> Bool { + _contentRect.origin.x <= x + 1.0 + } + + @objc open func isInBoundsRight(_ x: CGFloat) -> Bool { + let x = floor(x * 100.0) / 100.0 + return (_contentRect.origin.x + _contentRect.size.width) >= x - 1.0 + } + + @objc open func isInBoundsTop(_ y: CGFloat) -> Bool { + _contentRect.origin.y <= y + } + + @objc open func isInBoundsBottom(_ y: CGFloat) -> Bool { + let normalizedY = floor(y * 100.0) / 100.0 + return (_contentRect.origin.y + _contentRect.size.height) >= normalizedY + } + + /** + A method to check whether a line between two coordinates intersects with the view port by using a linear function. + + Linear function (calculus): `y = ax + b` + + Note: this method will not check for collision with the right edge of the view port, as we assume lines run from left + to right (e.g. `startPoint < endPoint`). + + - Parameters: + - startPoint: the start coordinate of the line. + - endPoint: the end coordinate of the line. + */ + @objc open func isIntersectingLine(from startPoint: CGPoint, to endPoint: CGPoint) -> Bool { + // If start- and/or endpoint fall within the viewport, bail out early. + if isInBounds(point: startPoint) || isInBounds(point: endPoint) { return true } + // check if x in bound when it's a vertical line + if startPoint.x == endPoint.x { return isInBoundsX(startPoint.x) } + + // Calculate the slope (`a`) of the line (e.g. `a = (y2 - y1) / (x2 - x1)`). + let a = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x) + // Calculate the y-correction (`b`) of the line (e.g. `b = y1 - (a * x1)`). + let b = startPoint.y - (a * startPoint.x) + + // Check for colission with the left edge of the view port (e.g. `y = (a * minX) + b`). + // if a is 0, it's a horizontal line; checking b here is still valid, as b is `point.y` all the time + if isInBoundsY((a * contentRect.minX) + b) { return true } + + // Skip unnecessary check for collision with the right edge of the view port + // (e.g. `y = (a * maxX) + b`), as such a line will either begin inside the view port, + // or intersect the left, top or bottom edges of the view port. Leaving this logic here for clarity's sake: + // if isInBoundsY((a * contentRect.maxX) + b) { return true } + + // While slope `a` can theoretically never be `0`, we should protect against division by zero. + guard a != 0 else { return false } + + // Check for collision with the bottom edge of the view port (e.g. `x = (maxY - b) / a`). + if isInBoundsX((contentRect.maxY - b) / a) { return true } + + // Check for collision with the top edge of the view port (e.g. `x = (minY - b) / a`). + if isInBoundsX((contentRect.minY - b) / a) { return true } + + // This line does not intersect the view port. + return false + } + + /// The current x-scale factor + @objc open var scaleX: CGFloat { + _scaleX + } + + /// The current y-scale factor + @objc open var scaleY: CGFloat { + _scaleY + } + + /// The minimum x-scale factor + @objc open var minScaleX: CGFloat { + _minScaleX + } + + /// The minimum y-scale factor + @objc open var minScaleY: CGFloat { + _minScaleY + } + + /// The minimum x-scale factor + @objc open var maxScaleX: CGFloat { + _maxScaleX + } + + /// The minimum y-scale factor + @objc open var maxScaleY: CGFloat { + _maxScaleY + } + + /// The translation (drag / pan) distance on the x-axis + @objc open var transX: CGFloat { + _transX + } + + /// The translation (drag / pan) distance on the y-axis + @objc open var transY: CGFloat { + _transY + } + + /// if the chart is fully zoomed out, return true + @objc open var isFullyZoomedOut: Bool { + isFullyZoomedOutX && isFullyZoomedOutY + } + + /// `true` if the chart is fully zoomed out on it's y-axis (vertical). + @objc open var isFullyZoomedOutY: Bool { + !(_scaleY > _minScaleY || _minScaleY > 1.0) + } + + /// `true` if the chart is fully zoomed out on it's x-axis (horizontal). + @objc open var isFullyZoomedOutX: Bool { + !(_scaleX > _minScaleX || _minScaleX > 1.0) + } + + /// Set an offset in pixels that allows the user to drag the chart over it's bounds on the x-axis. + @objc open func setDragOffsetX(_ offset: CGFloat) { + _transOffsetX = offset + } + + /// Set an offset in pixels that allows the user to drag the chart over it's bounds on the y-axis. + @objc open func setDragOffsetY(_ offset: CGFloat) { + _transOffsetY = offset + } + + /// `true` if both drag offsets (x and y) are zero or smaller. + @objc open var hasNoDragOffset: Bool { + _transOffsetX <= 0.0 && _transOffsetY <= 0.0 + } + + /// `true` if the chart is not yet fully zoomed out on the x-axis + @objc open var canZoomOutMoreX: Bool { + _scaleX > _minScaleX + } + + /// `true` if the chart is not yet fully zoomed in on the x-axis + @objc open var canZoomInMoreX: Bool { + _scaleX < _maxScaleX + } + + /// `true` if the chart is not yet fully zoomed out on the y-axis + @objc open var canZoomOutMoreY: Bool { + _scaleY > _minScaleY + } + + /// `true` if the chart is not yet fully zoomed in on the y-axis + @objc open var canZoomInMoreY: Bool { + _scaleY < _maxScaleY + } +} diff --git a/Pods/HotKey/Sources/HotKey/HotKeysController.swift b/Pods/HotKey/Sources/HotKey/HotKeysController.swift index 13bbe5c..e15db71 100644 --- a/Pods/HotKey/Sources/HotKey/HotKeysController.swift +++ b/Pods/HotKey/Sources/HotKey/HotKeysController.swift @@ -116,7 +116,7 @@ final class HotKeysController { // Ensure we have a HotKey registered for this ID guard hotKeyID.signature == eventHotKeySignature, - let hotKey = self.hotKey(for: hotKeyID.id) + let hotKey = self.hotKey(for: hotKeyID.id) else { return OSStatus(eventNotHandledErr) } @@ -178,5 +178,5 @@ final class HotKeysController { } private func hotKeyEventHandler(eventHandlerCall _: EventHandlerCallRef?, event: EventRef?, userData _: UnsafeMutableRawPointer?) -> OSStatus { - return HotKeysController.handleCarbonEvent(event) + HotKeysController.handleCarbonEvent(event) } diff --git a/Pods/HotKey/Sources/HotKey/KeyCombo.swift b/Pods/HotKey/Sources/HotKey/KeyCombo.swift index 0a0aa2f..c5d0c4a 100644 --- a/Pods/HotKey/Sources/HotKey/KeyCombo.swift +++ b/Pods/HotKey/Sources/HotKey/KeyCombo.swift @@ -8,7 +8,7 @@ public struct KeyCombo: Equatable { public var key: Key? { get { - return Key(carbonKeyCode: carbonKeyCode) + Key(carbonKeyCode: carbonKeyCode) } set { @@ -18,7 +18,7 @@ public struct KeyCombo: Equatable { public var modifiers: NSEvent.ModifierFlags { get { - return NSEvent.ModifierFlags(carbonFlags: carbonModifiers) + NSEvent.ModifierFlags(carbonFlags: carbonModifiers) } set { @@ -27,7 +27,7 @@ public struct KeyCombo: Equatable { } public var isValid: Bool { - return carbonKeyCode >= 0 + carbonKeyCode >= 0 } // MARK: - Initializers @@ -45,21 +45,21 @@ public struct KeyCombo: Equatable { // MARK: - Converting Keys public static func carbonKeyCodeToString(_: UInt32) -> String? { - return nil + nil } } -extension KeyCombo { - public var dictionary: [String: Any] { - return [ +public extension KeyCombo { + var dictionary: [String: Any] { + [ "keyCode": Int(carbonKeyCode), "modifiers": Int(carbonModifiers), ] } - public init?(dictionary: [String: Any]) { + init?(dictionary: [String: Any]) { guard let keyCode = dictionary["keyCode"] as? Int, - let modifiers = dictionary["modifiers"] as? Int + let modifiers = dictionary["modifiers"] as? Int else { return nil } diff --git a/Pods/HotKey/Sources/HotKey/NSEventModifierFlags+HotKey.swift b/Pods/HotKey/Sources/HotKey/NSEventModifierFlags+HotKey.swift index 02a6857..4476abf 100644 --- a/Pods/HotKey/Sources/HotKey/NSEventModifierFlags+HotKey.swift +++ b/Pods/HotKey/Sources/HotKey/NSEventModifierFlags+HotKey.swift @@ -1,8 +1,8 @@ import AppKit import Carbon -extension NSEvent.ModifierFlags { - public var carbonFlags: UInt32 { +public extension NSEvent.ModifierFlags { + var carbonFlags: UInt32 { var carbonFlags: UInt32 = 0 if contains(.command) { @@ -24,7 +24,7 @@ extension NSEvent.ModifierFlags { return carbonFlags } - public init(carbonFlags: UInt32) { + init(carbonFlags: UInt32) { self.init() if carbonFlags & UInt32(cmdKey) == UInt32(cmdKey) { diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock index 5287612..2ffc46d 100644 --- a/Pods/Manifest.lock +++ b/Pods/Manifest.lock @@ -1,20 +1,26 @@ PODS: + - Charts (3.6.0): + - Charts/Core (= 3.6.0) + - Charts/Core (3.6.0) - HotKey (0.1.2) - - SwiftLint (0.39.2) + - SwiftLint (0.40.3) DEPENDENCIES: + - Charts - HotKey - SwiftLint SPEC REPOS: trunk: + - Charts - HotKey - SwiftLint SPEC CHECKSUMS: + Charts: b1e3a1f5a1c9ba5394438ca3b91bd8c9076310af HotKey: ad59450195936c10992438c4210f673de5aee43e - SwiftLint: 22ccbbe3b8008684be5955693bab135e0ed6a447 + SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 -PODFILE CHECKSUM: bc53a454bd1292998c5646f550d93a401c3809d2 +PODFILE CHECKSUM: c2e80d933e60ddedf45d9176975578a449f44603 -COCOAPODS: 1.9.1 +COCOAPODS: 1.10.0 diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj index 39edc50..7494ffe 100644 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -3,47 +3,217 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ - 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */ = { + 2B03D5FD26A26B7D62142EA4492AEB83 /* SwiftLint-iOS */ = { isa = PBXAggregateTarget; - buildConfigurationList = AE7B4FB01588B9E6DF09CB79FC7CE7BD /* Build configuration list for PBXAggregateTarget "SwiftLint" */; + buildConfigurationList = 15C2581D1D019FF8D7F783E76C884B84 /* Build configuration list for PBXAggregateTarget "SwiftLint-iOS" */; buildPhases = ( ); dependencies = ( ); - name = SwiftLint; - productName = SwiftLint; + name = "SwiftLint-iOS"; + productName = "SwiftLint-iOS"; + }; + 9F0E6E0C8BF75091EADD98FA13B1A847 /* SwiftLint-macOS */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 0D0981C130B08F416BEB2EAEC0323E63 /* Build configuration list for PBXAggregateTarget "SwiftLint-macOS" */; + buildPhases = ( + ); + dependencies = ( + ); + name = "SwiftLint-macOS"; + productName = "SwiftLint-macOS"; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 0DEDAE490C1A3FA9161CD695C0645004 /* Pods-PiBar-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F2A08F3DB0E5A7EB3AC5560E0B4B721 /* Pods-PiBar-dummy.m */; }; - 29784D27868C289298CBBC341C089C58 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA77483581310D677457C751AE34FE4 /* Key.swift */; }; - 5AC7E7B8F7316B6F468E3A7919954BFB /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0A08161AAA89131150FB117FA3FFD44 /* AppKit.framework */; }; - 6329F2E68803BCDBFC3C3A48DF5109E3 /* HotKey-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CED4EFAB4A1A56BB51A65EDBB6CAF5E /* HotKey-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 753C094F16E687CC3C027B49F9A6DE3C /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDD6B68A3C6BEFE01D5859A9C46AB5 /* HotKeysController.swift */; }; - 9C995A1C6CEE3603CBD3C829D8ECEF8C /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7173204885C9B0546569E846A3DDAF6F /* HotKey.swift */; }; - A3543A70DF8931D1D0C5FA57A6B0FB4B /* HotKey-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C9679204B9F60619EF9A77AC75DAD2A9 /* HotKey-dummy.m */; }; - A7CD375B656BAC38CD49FCF05366BA6E /* Pods-PiBar-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = E0B1534582A88AC85E5869A791797730 /* Pods-PiBar-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B1E75E67962EF3891467FAA026346869 /* NSEventModifierFlags+HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B791A837FA788207F296E05F0352D37 /* NSEventModifierFlags+HotKey.swift */; }; - B690623CBB2C64D853AC854EA9B3DD60 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9079B716F46D611EBFE1541810454743 /* KeyCombo.swift */; }; - BB3AF94A65AEC246F6A55EF4C26B0C15 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 123D6DC4B5EFB570985CEFACFC1B93CE /* Cocoa.framework */; }; - C0E9E705DDA754D6E1EC52A050650B70 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 123D6DC4B5EFB570985CEFACFC1B93CE /* Cocoa.framework */; }; - F9FBEA632006006292C99342FF7CC2C9 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFBC9A90999F22CE48B198862DFC5B5D /* Carbon.framework */; }; + 00F52B5B3C90538FE25AEBD9DCC93249 /* ChartDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 711AC91C488BD504E029B6291ED7CC85 /* ChartDataProvider.swift */; }; + 01618174B06DD11428CA21BD4F996704 /* ComponentBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 244CD4B7FF203C32BE80021EA21133D8 /* ComponentBase.swift */; }; + 02DB3B5C782BC3DEF6B921B9D049201D /* BubbleChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E071CDADFA60D6619D14E6880D074D6F /* BubbleChartDataEntry.swift */; }; + 0388D8402985CBE0DB060BB0D7CCB2ED /* Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BBD20530C653732EFAD77F07BBC9D7E /* Description.swift */; }; + 03D8F8D16FAF2B28113DB82186DBC66F /* BarLineChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2C4115A525E3C81D8EBEA8B798B98D /* BarLineChartViewBase.swift */; }; + 0769C281810E13C0985B2A414F20ADDE /* XAxisRendererHorizontalBarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2934BC0E132E9A6DD9E800B10339B639 /* XAxisRendererHorizontalBarChart.swift */; }; + 09D2EA12CED518BAA4BCB6A707FA3125 /* TriangleShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1091D499CF4038119CC9CD9E9E14A98 /* TriangleShapeRenderer.swift */; }; + 0A30CC2BF450A16F1F4C39C95CFA3A66 /* BarChartDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1086EF59980430D14ECD1CFA9CDCC /* BarChartDataProvider.swift */; }; + 0E7DC6451BEFB26E3A47621C0B7F1C16 /* ViewPortJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC2506537E1D9F83506E6E877AA1A869 /* ViewPortJob.swift */; }; + 1132976D856E1857C4CD9989B9A8EBCE /* PieChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6677816AA5273790F636847E7EB6F6FC /* PieChartView.swift */; }; + 12A718BC5CDEB44AA6B58C8EE7C13128 /* ChartColorTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0557E250A1067B869EC2ED6273F65A28 /* ChartColorTemplates.swift */; }; + 13D3AB43B01F6D78090806390BD29B7E /* BarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C743FEF39ED1DAC0D7F7642E996B602 /* BarChartDataSet.swift */; }; + 171D7FC46EC56CF9B16D97877A176C10 /* ChartUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE6A24B81FDB4ABAB0C803A02370576 /* ChartUtils.swift */; }; + 1AF2A57E2B15E7DB326A38D8D8A4FBE2 /* ChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0615B23DDBFBB6687E057CDF87DE4032 /* ChartViewBase.swift */; }; + 1C2DB744A980164665CE687581222A12 /* Charts-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D07A24C25ECC0790372BBD1AC71BAB2 /* Charts-dummy.m */; }; + 1D2BEF7D292100F700E00314C6FA6FAF /* CandleStickChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5556E1C487AEE536DF5EE084D4C99330 /* CandleStickChartView.swift */; }; + 1EA6964A5C78B205DFC45AFC08637DF8 /* LegendEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD9C7FE782710481AB2BFE38DB1C959 /* LegendEntry.swift */; }; + 1F33F0B7F8701E9A20F90C6A32B2CA03 /* Platform.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C2876FFF1E2A2C89AD7BAF3849EAF3 /* Platform.swift */; }; + 1F91BEF50B63CA55CA641D3367FCA3F3 /* CandleStickChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F797AE00C211197E2F8A9C08654968 /* CandleStickChartRenderer.swift */; }; + 22681421488F1E799B4C8FFC0A77F29B /* ZoomViewJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CFD73408F2B072CBAD933AEDB4A812 /* ZoomViewJob.swift */; }; + 2268DE11251696977088EB5D4310171E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 23370B0E20140BBD63087288D6A26EBA /* Foundation.framework */; }; + 25D675B271C3E073CF2C27B945FAADBB /* RadarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4064E7B401FA5A37E039D20EC0A78C0 /* RadarChartView.swift */; }; + 2652DBC582FD2BF6AA5B2A40B66020EC /* CircleShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90F689617DB535EFA72FB84EF7D6B851 /* CircleShapeRenderer.swift */; }; + 26EFCEF521B95D2CCB19E5535408A1FA /* Fill.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7C4C3115D6C19C96BEBD5845AC9EE7 /* Fill.swift */; }; + 27303BB4BDD2C456C1DEC36087E756B4 /* ICandleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C789B0920F0049AB6FD589B0D61268E /* ICandleChartDataSet.swift */; }; + 29784D27868C289298CBBC341C089C58 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29720EAA9D8F0EA20B34CCA01DBB8A3A /* Key.swift */; }; + 2AFDC0B52F5347C726EAAD89365E3978 /* PieChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F440F9337268D36E8AB546D66225045 /* PieChartDataSet.swift */; }; + 2E91A4CB83F9DC6BDDAE907A38FE4648 /* BubbleChartDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D96B13A0BB622CC7F2E5B57D4C83DA5 /* BubbleChartDataProvider.swift */; }; + 2F819FF7F7721DA042E977EF91BA545D /* CombinedChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9151D7F121CB0A7AA14C4E1A998292A8 /* CombinedChartRenderer.swift */; }; + 31918F06050D6445E06FEDF8E2183AF6 /* XAxisRendererRadarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = E68CC3445CA2353DD389F1C888C973E4 /* XAxisRendererRadarChart.swift */; }; + 32B122E4A8FECD28F192174EA3A4C704 /* MarkerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3294664ECF61BC3B23D88E71F409D2F5 /* MarkerView.swift */; }; + 34BC5667BA39781B4F70C9AC0518E537 /* CombinedChartDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907823E94DFCC439CBE000E0DF34B873 /* CombinedChartDataProvider.swift */; }; + 35C57ED01BE2D7FD6F4DACD96F5EDFE1 /* IFillFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9C8B871EE3F4A0CDDC70BD5DBA100C /* IFillFormatter.swift */; }; + 39E13C8ED0C32E9EC0B353CA45C70CF2 /* AnimatedMoveViewJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E1CE13C1A77ED830381DAD0172B74 /* AnimatedMoveViewJob.swift */; }; + 3A5078F021BB004E2F4463F360D41391 /* HorizontalBarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 368473E61A6407A4004C227203988F08 /* HorizontalBarChartView.swift */; }; + 3BDFA459949B787FF4BA39DE9BF523AB /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D486CE56C1DCFDFACC043AA9637506F /* AppKit.framework */; }; + 3D57709B53538839313AA6A6DC6E322C /* ILineRadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFD17F9DE444E07260998B08AC164B82 /* ILineRadarChartDataSet.swift */; }; + 3EAC9B9D96C569653EF71750289EB0D0 /* CandleChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A5360DB0FF01F3EB3858F4E173280FF /* CandleChartDataEntry.swift */; }; + 474E946BEBC8E12EA9C8D766BDFCEEF5 /* Platform+Graphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A5C32B9668A5AA30811484A9D87755 /* Platform+Graphics.swift */; }; + 4975CACC893A427910F64270C28E7275 /* RadarChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7B30F113D09D8675C4E2BC58753A160 /* RadarChartData.swift */; }; + 4994E7B2E69CF616E8E7ECD19014CAE0 /* CandleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57284B3CA2A39445B9854516C306774 /* CandleChartDataSet.swift */; }; + 4BBFCE1FBB68AAFB91FF682CD80B9F69 /* ChevronDownShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81CAD649D17F3E160C64BE89A140E986 /* ChevronDownShapeRenderer.swift */; }; + 4D9563B240B9127EDF6FBA5A21180FD8 /* PieChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA83EE25C715BDC7405BA5BABBA98CF3 /* PieChartData.swift */; }; + 4DB24C948D1C4F4FB43DAA833D049885 /* LegendRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8181E0C1E9520A83B96573A045C399 /* LegendRenderer.swift */; }; + 5128310DB4D59B51642B2A13972C3434 /* IAxisValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395C5E41440F4B38B3F24BD7B1650819 /* IAxisValueFormatter.swift */; }; + 53772EF4F22DD42A63551C4CD687E9A6 /* LineRadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D2815154251C04CBDCAA862B8C795A /* LineRadarChartDataSet.swift */; }; + 58398B0E03224E5EBE7FCFAD62F8FB69 /* ILineChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDA33A93FDD63744055B8289AEBFA6D /* ILineChartDataSet.swift */; }; + 5847AEA5F6021F082A9238BDD5801079 /* Platform+Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = E68DDBEF0B4A92803CA6B07280F6B1B7 /* Platform+Accessibility.swift */; }; + 5B96DE0FB615C832ACB06B9E16B49740 /* IValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC1890CFB0B6485DD3C43DEBACD8FF8 /* IValueFormatter.swift */; }; + 5C790091CB1B0D41E23A4AE5FA727983 /* IBubbleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 414364F817C1844C9E38EACB1E26293E /* IBubbleChartDataSet.swift */; }; + 5D79FD33591AEAEB1B9BF86EC4EA3EA6 /* BubbleChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2E4EED98013725C32276E547328091 /* BubbleChartRenderer.swift */; }; + 5DA6F4FC8413B90BD793D0AE181C2AA9 /* SquareShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E941D8EBB13F56A36AC17461EB7F12F2 /* SquareShapeRenderer.swift */; }; + 5E27C6B54F72D3C34AB6605772DF7E70 /* LineScatterCandleRadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B2134DEB4D8B303CF159710CC430A5 /* LineScatterCandleRadarChartDataSet.swift */; }; + 61B753A042411BCF77F768D958106A5C /* RadarHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E14E27F329B3F8FA97C43F34CDBC65B /* RadarHighlighter.swift */; }; + 62BEAD080E107195393F8ABBA8C81572 /* BarChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31EFCCC9A4CA02BC3D8C8B9C1A90346 /* BarChartData.swift */; }; + 62C2F7F87CA413E2407F1F8DECA88949 /* IChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5BF7686C4F3C30198A4CD9FA008367E /* IChartDataSet.swift */; }; + 6329F2E68803BCDBFC3C3A48DF5109E3 /* HotKey-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D22C34DDE627B4250229FD45B6BB6511 /* HotKey-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 640C152125D86F7A3C9BDD7D18563CBA /* Platform+Gestures.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C02E3862AE6E4C1DCC69993DF57FFD /* Platform+Gestures.swift */; }; + 644CBE15115BFEDB16BF372F81CFF076 /* PieRadarHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9445753CB81AE0B68A4C5FFCA2C40C26 /* PieRadarHighlighter.swift */; }; + 64A0821B4F9954E56639DAF9BD7E575C /* Transformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC65C724FEDB749BAA8000308D6829 /* Transformer.swift */; }; + 66B829842549EECCE91E61D34FFABCBA /* ScatterChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C2D79C8678FAB439F11F69E03E4FB /* ScatterChartData.swift */; }; + 66FC952C43367208D641932AEB59DA8A /* LineChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5DCF57B389710682DCE9D8565DF8E2 /* LineChartDataSet.swift */; }; + 675D24D585A2506762D77E6052517E00 /* IPieChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DAB85F670221A85ADDCF9EF6A2CE8A /* IPieChartDataSet.swift */; }; + 6801E6D885988880F502EFA919360446 /* DefaultAxisValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2043E61F9E8D76F3F95A3DA93FC9DDA /* DefaultAxisValueFormatter.swift */; }; + 683F5BAD171DD257F3EC9472323B4BC7 /* RadarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94327B674E713647F8EA01260F95CBFA /* RadarChartRenderer.swift */; }; + 6A532F6858B2D5219D76BEFE1E74A5A6 /* HorizontalBarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83284BAFFC99DA4BDE43C336ECA3A765 /* HorizontalBarChartRenderer.swift */; }; + 6FDD5EA5214B8CDC0E747916F07D74C2 /* YAxisRendererRadarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3F4081A56764B8F384626813FAE68A /* YAxisRendererRadarChart.swift */; }; + 709152D66394DCE1D7D3802C65090118 /* IBarLineScatterCandleBubbleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48207F38EACD2A5AADE370A4ACDB04E3 /* IBarLineScatterCandleBubbleChartDataSet.swift */; }; + 7320CBA29BE1D473CA5BE26B1D226D04 /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D783E4FB64E5B23815B174E961651CF /* Animator.swift */; }; + 74D4DC9B702FBC147C03965E9F271128 /* CrossShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 593578BB5D43FC02FD1CD89B75502607 /* CrossShapeRenderer.swift */; }; + 753C094F16E687CC3C027B49F9A6DE3C /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD40E15BBF8D4A43E0EF41542A297D9D /* HotKeysController.swift */; }; + 75B22CB8C580F73B117214B061436099 /* IMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67AF26BDBC5C1011C84B823325516A65 /* IMarker.swift */; }; + 7605565F28B5C078923EE0A3F5D7C86E /* ChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC98BCCB70B4149654FCDFFEA08761B /* ChartData.swift */; }; + 7731319719C1878AE7AF9D78705712AF /* Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE92380CA74FDE527DC33A8DD1281DE3 /* Range.swift */; }; + 794D228A0613A523C567E2154D5AF920 /* Pods-PiBar for iOS-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0804594DEA4D52747B3B985663B23B48 /* Pods-PiBar for iOS-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ACE9E9A28CB14F6DB0D7218C8B50917 /* BubbleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D00E3904DC644D85307DB616AE70704 /* BubbleChartDataSet.swift */; }; + 7AF1F2C1A31F465C48612CF03B985908 /* Pods-PiBar for macOS-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C0F4EDBCCFB5FCA58A057FE69FACE3A5 /* Pods-PiBar for macOS-dummy.m */; }; + 7D605E2EF1E737244832E3329AE89746 /* BarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F066DE17AF257744F13593C11AB3B414 /* BarChartRenderer.swift */; }; + 7EA6B81624538BCC292467DC0E7832D0 /* BubbleChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9EDF9643C07C7796A43915A3759062 /* BubbleChartData.swift */; }; + 8104AC0D8B60EEC6FB5C73EAFA1934AA /* PieRadarChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9645910EDC22A164E994394CCAFB4C /* PieRadarChartViewBase.swift */; }; + 81FCFDD76834B83BF46D3741C79FF29A /* MarkerImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46F5F9E574E9C142D41BBEEA183FD38 /* MarkerImage.swift */; }; + 83907D8546A31C4C4B1E31DA90A0CD77 /* ChartLimitLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 639840868C7F877C57ED23EE194D190C /* ChartLimitLine.swift */; }; + 83BBD232FE2EA04587B21C81B73AFC09 /* ChartDataEntryBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100506A7D371448771162249393D1D1 /* ChartDataEntryBase.swift */; }; + 846265AE5C245E369A93B951583BB63C /* Pods-PiBar for macOS-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = FBFE3526033234DC9BC0B3FE05F43A94 /* Pods-PiBar for macOS-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8606577BE49942FFBE9764F6BAC993A8 /* BarHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2772813C4462D3FE333403E942BAFC58 /* BarHighlighter.swift */; }; + 8CBA559D0C2045A8E6F7F9922C553F0F /* CandleChartDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2733644CE726BB60AB6E7E43B857B0 /* CandleChartDataProvider.swift */; }; + 8D62FB3504F2C9B9E0910E493637F7BE /* BarChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70832718C7F930DF0C0076D9B7B0D795 /* BarChartDataEntry.swift */; }; + 8E01F38A1667D8B2C57E20400D6BB425 /* LineRadarRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7378111D9560C5E98B0B95AF0F67461B /* LineRadarRenderer.swift */; }; + 8EE80878B32207C5EC1712D83F04983A /* ChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D200D11F29B73A83E32A3482E89EF5C3 /* ChartDataSet.swift */; }; + 91704461340ABFBEB88425DCC9867890 /* HorizontalBarHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF77D965BBBC24829165C077469F2888 /* HorizontalBarHighlighter.swift */; }; + 91FA70B412F137846836139ED890FF6C /* ChartBaseDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A1E4D59B5E97262EBD7772CB794554 /* ChartBaseDataSet.swift */; }; + 930F4FD273BB41A8D1BB1F07608B3028 /* RadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09667EA54576A5F100E3D33F233BAEAA /* RadarChartDataSet.swift */; }; + 93966518847AB0F104DFCC9DC5B63197 /* ChartHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3523B89A99586703D14801648519131 /* ChartHighlighter.swift */; }; + 9400854C32DFF16FD068D280CD43176F /* AxisBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167044BFFBE9F6C5497AA6B4CC814953 /* AxisBase.swift */; }; + 94921CD3D3FBA3D6572678B1F59AA957 /* TransformerHorizontalBarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3921EB658010A1A98BE9C22BB83C2F3C /* TransformerHorizontalBarChart.swift */; }; + 970B9C4530EA8ACC31B005CE4523C91B /* AnimatedZoomViewJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A24BBDE802F89F581D8B87167964E4B /* AnimatedZoomViewJob.swift */; }; + 991A4EA362C241D77DF184594E226E93 /* ScatterChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05851806E9663A3EBD3753C7BA948EFF /* ScatterChartView.swift */; }; + 9BE82DE86F384EAD7B0B42894C0ECD75 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4651DB1A3745D6DE10BBAAC032A11263 /* Carbon.framework */; }; + 9C081B18BB1A54D953EB89605319AA77 /* Legend.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C864721875A16D2FDC517AF11C2C14 /* Legend.swift */; }; + 9C995A1C6CEE3603CBD3C829D8ECEF8C /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF444F89EF887198F973A22E19B0604 /* HotKey.swift */; }; + 9F668D7B0F3CEA3608D305A013A7BE48 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C93CD7E7AEE53FCAA9BFF35BAFA1EB /* Highlight.swift */; }; + A3543A70DF8931D1D0C5FA57A6B0FB4B /* HotKey-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = BD360DE8DD14870AB4A85343429BD5DE /* HotKey-dummy.m */; }; + A688509392FC14A799979CEA50908A2D /* DataApproximator+N.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5EB703D9C96F90868593E905D7AD02 /* DataApproximator+N.swift */; }; + A710DCACFF8FFEA66D7ACC5D1BD95C1D /* XAxisRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C4E105F3E9D1457850D45D2ADB198B /* XAxisRenderer.swift */; }; + A7E344DB25C4335481A9FE224A1C47E9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 23370B0E20140BBD63087288D6A26EBA /* Foundation.framework */; }; + A86CEF7063CDF44017CBFC67DEA1168E /* ChevronUpShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E309C4D380DECA006E761B15CD8FBF99 /* ChevronUpShapeRenderer.swift */; }; + A95748A4D50EEB26B6290061C34DAA6D /* AnimatedViewPortJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F29D2077DC8B40B0C8C7273B3D8C402 /* AnimatedViewPortJob.swift */; }; + AA39F0560285BF5448411D429ED9408D /* IScatterChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 145435D9A044342F696BBCE5962DBC6D /* IScatterChartDataSet.swift */; }; + ABC6878E21B62261BBB4E1631E5EDAF1 /* ChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6FF5886BFE76D65CBC5AFB034D6B7F /* ChartDataEntry.swift */; }; + AD1E6E99CE3807BF3DEE1BEBE6E3CAF2 /* BarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEA3878A4F44FE2E54F0DC0C49919E5 /* BarChartView.swift */; }; + AEAB95AFD2529CDAF3A10D90A165CF16 /* BarLineScatterCandleBubbleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0DA4BB9F332363E6A51E6D8B7632AE /* BarLineScatterCandleBubbleChartDataSet.swift */; }; + AF34EC6CDB336F35ACD18C2CA8428251 /* PieChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60032EFEF6BA80A234E3DCFFF5871937 /* PieChartDataEntry.swift */; }; + B00A1568A89DD820BC9063DC6961540D /* XAxis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B226E6F6386306EDE864829641C400 /* XAxis.swift */; }; + B14341E138DC6D05193C2866864E9DEC /* AxisRendererBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04AC0522545DFF6FF42E62E0A4D4B2D /* AxisRendererBase.swift */; }; + B1E75E67962EF3891467FAA026346869 /* NSEventModifierFlags+HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444F78B73604CAD35ED26ACF855F2CCD /* NSEventModifierFlags+HotKey.swift */; }; + B45CE2B91939EC0EA7898C1C6BA16367 /* ILineScatterCandleRadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 352B53865FA675D09EA263AEC626AB09 /* ILineScatterCandleRadarChartDataSet.swift */; }; + B65DAD4280582DBED3A5A2C62185B389 /* YAxisRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C87FEE09C0356FD909EB6A0ABC346 /* YAxisRenderer.swift */; }; + B690623CBB2C64D853AC854EA9B3DD60 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0196FF4461C772C4FD219EAE800A0EA5 /* KeyCombo.swift */; }; + B87DABFAA95CDA5DFDDD2F409FDD5DF3 /* YAxisRendererHorizontalBarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1F32D4A13450CE8BA678EC0C700137 /* YAxisRendererHorizontalBarChart.swift */; }; + BB75859BDCF578D67C08E6E6741701EB /* PieChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1FD5EC58DABC62B263392FBC987A32 /* PieChartRenderer.swift */; }; + BCD42880A54D247B7A68AC06335D0AF5 /* YAxis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA3D36C8146CCABEEA88FA761FC5FA9 /* YAxis.swift */; }; + BDD367ED4BB5510C86658115EE6BE71E /* CombinedHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7B8B0029F1732FC48755E3C038CFCD /* CombinedHighlighter.swift */; }; + C031EFD3013B5803C3B7A6D07E4721C0 /* CombinedChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D286496CABCB5A7855C35586A80EB12 /* CombinedChartData.swift */; }; + C2012E1E9FD44B75ECF4B4B3D3782293 /* IndexAxisValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1755627B9AD697B1A949B2B8F4FBABA3 /* IndexAxisValueFormatter.swift */; }; + C45A1A018758BC37A59C89114F9A4F75 /* Platform+Touch Handling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A43672B987BF172D047574BBC71FF0 /* Platform+Touch Handling.swift */; }; + C5880AE977F94A1AE25E756A805B7B0B /* LineChartDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A28B259EF5E09B69DB6A976AED8FB69D /* LineChartDataProvider.swift */; }; + C5A3430178D9993D8A5A13864AE40A45 /* ScatterChartDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98DA53EC25C12781C638CC02D8D10934 /* ScatterChartDataProvider.swift */; }; + C6F4045EAF5410768B595ED98863A250 /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16AFDF3BFC323B14A3D741F03C6649B /* Renderer.swift */; }; + C72D7A57303FE4E7E25AB747DD3A85AC /* RadarChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A613E745688FF02C525D10E8768C8471 /* RadarChartDataEntry.swift */; }; + C742D0AA9431960E21B6203AF6D2F62B /* PieHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E70F95A461C1D95565C17F1799B5B13D /* PieHighlighter.swift */; }; + C798D8F553DB176CD793E27B4C309DC6 /* ChartDataRendererBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9802DF36DF676772550A3ADD37D8B03B /* ChartDataRendererBase.swift */; }; + C7F30DCF3631591639429F38CDA24EBC /* BarLineScatterCandleBubbleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B933C303CE788551510DD2D56A4E6D13 /* BarLineScatterCandleBubbleRenderer.swift */; }; + C8092EC19C870273D18B562A7EE9CD0F /* BarLineScatterCandleBubbleChartDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E78998534AC40C7E72F90ACE6493A635 /* BarLineScatterCandleBubbleChartDataProvider.swift */; }; + CAE986D939019B7726C896B88AC34B82 /* ScatterChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 875211C8A1BA177A2E62C99113C88F00 /* ScatterChartDataSet.swift */; }; + CB6E235EB0A5B896F9D1815877757EF9 /* CandleChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C37FA327EF8395FA55D4E88B26D1A4 /* CandleChartData.swift */; }; + CDABB0AA47F0EEE957FCE629F8D2DB6B /* BubbleChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7563D9353E9BF0AC1CC19D8CDE58CFAC /* BubbleChartView.swift */; }; + CDFF1CFDBA33AF524DA0CDA8C338B44A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E5A18E310985E019B14966EC691669B /* Cocoa.framework */; }; + CEC814CFD549BCDDAB65F5918969182E /* IHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 490C1B82D605ED351F94229382153A7F /* IHighlighter.swift */; }; + D68F93293888B6776369185028CB60B0 /* IBarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D94CEE0307835B9074C934FF28069FA9 /* IBarChartDataSet.swift */; }; + D6E011A7BA193945CEA69517E40CBD48 /* XShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D11ACD2351A43DE2C2E4F582CBD3B7 /* XShapeRenderer.swift */; }; + D7B623CFD7231E00FB60419AA17A3E82 /* MoveViewJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BF88949AD6C4860FC1A6C545B73484 /* MoveViewJob.swift */; }; + D885D137C7BBBBA479BDB92A72B7D04E /* Platform+Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005B336D0EA7C413B24A9172649E6BD7 /* Platform+Color.swift */; }; + D9687FA7E8A77056DC5C8444EEA904E5 /* BarLineScatterCandleBubbleChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69ED2D49A9B4002A9B7D538E22277D27 /* BarLineScatterCandleBubbleChartData.swift */; }; + DC28EC495C8806D9B9C0CB0C2F8E8673 /* LineScatterCandleRadarRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08327D3E630948798A6E2BA498EBCD67 /* LineScatterCandleRadarRenderer.swift */; }; + DD3CC52D093C0DF2CF8109C55CFFCD85 /* DefaultValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4711142CDB8852FDF587823F7A8D3A0A /* DefaultValueFormatter.swift */; }; + DDCAF53B6EE94F6ADF428036C2A85244 /* DataApproximator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D9BCE3CA31180EB6986D417D808265 /* DataApproximator.swift */; }; + E2FF6F533C0CFB1461BA3D7B76B48A9C /* DefaultFillFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2661A7453378228C7712D2E842C7F7E4 /* DefaultFillFormatter.swift */; }; + E3EDB632F407232103226EB2AF8C239D /* Charts-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0837BF2D2874FB3203FC7EDA183F9012 /* Charts-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E4131026AAE1E3333FB46E14CED55F2A /* ViewPortHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95659CAA2E2368804ADD057DD08D53D9 /* ViewPortHandler.swift */; }; + E73D9F2950182C774E50E891F75C1BB4 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8132C4EA18AE50A4770D3FECF791AFA /* LineChartView.swift */; }; + E999737BF0D21446B7FFE4DA783EACB2 /* ScatterChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1AE3079B10A1A511DBC474F7048B98E /* ScatterChartRenderer.swift */; }; + ECC149B136D4D63AF251912E051A142F /* IShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5635AFFDCA66E46FC969397D6C491F60 /* IShapeRenderer.swift */; }; + ED58A23EB5289726B5D74EFEEA590BDD /* LineChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D9AEFEF86395C5567A2B35A6A3ECE8 /* LineChartData.swift */; }; + ED652BAC315BB6FDDEB8C02789ED3D3D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E5A18E310985E019B14966EC691669B /* Cocoa.framework */; }; + EFB62053CDB41361478C397163454F5E /* Pods-PiBar for iOS-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A763B8F2ADB3B165DDA2FF6BBBA8E29 /* Pods-PiBar for iOS-dummy.m */; }; + F1B3693550BD159B7E910C182326A44B /* ChartAnimationEasing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5997095B5B3486DEC3BF9839A20242 /* ChartAnimationEasing.swift */; }; + F2C1EF0010084FA2B769B019EBD6D909 /* CombinedChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A023BD4C2C00CB8839587D670DD14CD /* CombinedChartView.swift */; }; + FC378CAAA9FD5B4B156B982EBDB12BA3 /* IRadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6DBF8160FC08BFD2455A8052AA94B7 /* IRadarChartDataSet.swift */; }; + FEFD4F20D5515E1D596FCFA083EB05D7 /* LineChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB9DA008E2880512CADF7159422D8A2 /* LineChartRenderer.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 0F27C02292C675A2D7AF4DF1D1D00110 /* PBXContainerItemProxy */ = { + 0391AC22223E7714EBFE9A97D49B1118 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9F0E6E0C8BF75091EADD98FA13B1A847; + remoteInfo = "SwiftLint-macOS"; + }; + 2B6E3223817FB0BB59F76C35B360A31C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2B03D5FD26A26B7D62142EA4492AEB83; + remoteInfo = "SwiftLint-iOS"; + }; + 4EB585088F1252F8E9E2E2C41BEEBF99 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = 52B60EC2A583F24ACBB69C113F5488B9; - remoteInfo = SwiftLint; + remoteGlobalIDString = BF03B0B2B8B052092CB5F90CC5FB3757; + remoteInfo = Charts; }; - 8D7EB7FF347D5F06486B82FC5EF3E49C /* PBXContainerItemProxy */ = { + C5B8520C09E77C7BA38315A81B959B26 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; @@ -53,43 +223,220 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 123D6DC4B5EFB570985CEFACFC1B93CE /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; - 3189FE65632B209B3608F60B0CC63455 /* HotKey.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = HotKey.release.xcconfig; sourceTree = ""; }; - 39D6FC83A74766CE2022EE87101A6CAE /* HotKey.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = HotKey.debug.xcconfig; sourceTree = ""; }; - 3C1FE6D309736C69BAF6F20B1F52D9BF /* Pods-PiBar-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-PiBar-frameworks.sh"; sourceTree = ""; }; - 4F2A08F3DB0E5A7EB3AC5560E0B4B721 /* Pods-PiBar-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-PiBar-dummy.m"; sourceTree = ""; }; - 5A2E111A2B15CDC74756281684BD9272 /* Pods-PiBar-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-PiBar-acknowledgements.plist"; sourceTree = ""; }; - 5CED4EFAB4A1A56BB51A65EDBB6CAF5E /* HotKey-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "HotKey-umbrella.h"; sourceTree = ""; }; - 67FDD6B68A3C6BEFE01D5859A9C46AB5 /* HotKeysController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HotKeysController.swift; path = Sources/HotKey/HotKeysController.swift; sourceTree = ""; }; - 716355C89B577E00268BBCDDABDD6654 /* Pods-PiBar-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-PiBar-acknowledgements.markdown"; sourceTree = ""; }; - 7173204885C9B0546569E846A3DDAF6F /* HotKey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HotKey.swift; path = Sources/HotKey/HotKey.swift; sourceTree = ""; }; - 73C0DC168EBF587BFB7F868747092C0C /* HotKey-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "HotKey-Info.plist"; sourceTree = ""; }; - 7590F3CB1E12178036147CF9EE2744DF /* Pods-PiBar.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-PiBar.modulemap"; sourceTree = ""; }; - 9079B716F46D611EBFE1541810454743 /* KeyCombo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeyCombo.swift; path = Sources/HotKey/KeyCombo.swift; sourceTree = ""; }; - 93AE2864FC4C28D40DE2C9BDF459C9F2 /* Pods-PiBar-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-PiBar-Info.plist"; sourceTree = ""; }; - 9B791A837FA788207F296E05F0352D37 /* NSEventModifierFlags+HotKey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSEventModifierFlags+HotKey.swift"; path = "Sources/HotKey/NSEventModifierFlags+HotKey.swift"; sourceTree = ""; }; + 005B336D0EA7C413B24A9172649E6BD7 /* Platform+Color.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Platform+Color.swift"; path = "Source/Charts/Utils/Platform+Color.swift"; sourceTree = ""; }; + 0196FF4461C772C4FD219EAE800A0EA5 /* KeyCombo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeyCombo.swift; path = Sources/HotKey/KeyCombo.swift; sourceTree = ""; }; + 036C87FEE09C0356FD909EB6A0ABC346 /* YAxisRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = YAxisRenderer.swift; path = Source/Charts/Renderers/YAxisRenderer.swift; sourceTree = ""; }; + 0557E250A1067B869EC2ED6273F65A28 /* ChartColorTemplates.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartColorTemplates.swift; path = Source/Charts/Utils/ChartColorTemplates.swift; sourceTree = ""; }; + 05851806E9663A3EBD3753C7BA948EFF /* ScatterChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ScatterChartView.swift; path = Source/Charts/Charts/ScatterChartView.swift; sourceTree = ""; }; + 0615B23DDBFBB6687E057CDF87DE4032 /* ChartViewBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartViewBase.swift; path = Source/Charts/Charts/ChartViewBase.swift; sourceTree = ""; }; + 0804594DEA4D52747B3B985663B23B48 /* Pods-PiBar for iOS-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-PiBar for iOS-umbrella.h"; sourceTree = ""; }; + 08327D3E630948798A6E2BA498EBCD67 /* LineScatterCandleRadarRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineScatterCandleRadarRenderer.swift; path = Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift; sourceTree = ""; }; + 0837BF2D2874FB3203FC7EDA183F9012 /* Charts-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Charts-umbrella.h"; sourceTree = ""; }; + 09667EA54576A5F100E3D33F233BAEAA /* RadarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RadarChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift; sourceTree = ""; }; + 09CFF440A13A62AE14A558484BC4B5D0 /* HotKey.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = HotKey.release.xcconfig; sourceTree = ""; }; + 0A1067A6A80D32F5634C0E57B31CCD60 /* Pods-PiBar for macOS-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-PiBar for macOS-frameworks.sh"; sourceTree = ""; }; + 0C789B0920F0049AB6FD589B0D61268E /* ICandleChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ICandleChartDataSet.swift; path = Source/Charts/Data/Interfaces/ICandleChartDataSet.swift; sourceTree = ""; }; + 0D07A24C25ECC0790372BBD1AC71BAB2 /* Charts-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Charts-dummy.m"; sourceTree = ""; }; + 0DCE4277E1341B290A8E556C5C2B19FC /* Charts.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Charts.release.xcconfig; sourceTree = ""; }; + 0E3F4081A56764B8F384626813FAE68A /* YAxisRendererRadarChart.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = YAxisRendererRadarChart.swift; path = Source/Charts/Renderers/YAxisRendererRadarChart.swift; sourceTree = ""; }; + 0F29D2077DC8B40B0C8C7273B3D8C402 /* AnimatedViewPortJob.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedViewPortJob.swift; path = Source/Charts/Jobs/AnimatedViewPortJob.swift; sourceTree = ""; }; + 11FC65C724FEDB749BAA8000308D6829 /* Transformer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Transformer.swift; path = Source/Charts/Utils/Transformer.swift; sourceTree = ""; }; + 145435D9A044342F696BBCE5962DBC6D /* IScatterChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IScatterChartDataSet.swift; path = Source/Charts/Data/Interfaces/IScatterChartDataSet.swift; sourceTree = ""; }; + 167044BFFBE9F6C5497AA6B4CC814953 /* AxisBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AxisBase.swift; path = Source/Charts/Components/AxisBase.swift; sourceTree = ""; }; + 1755627B9AD697B1A949B2B8F4FBABA3 /* IndexAxisValueFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IndexAxisValueFormatter.swift; path = Source/Charts/Formatters/IndexAxisValueFormatter.swift; sourceTree = ""; }; + 1A763B8F2ADB3B165DDA2FF6BBBA8E29 /* Pods-PiBar for iOS-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-PiBar for iOS-dummy.m"; sourceTree = ""; }; + 1C699EB5CCB69D147DDF43F493B1D1D6 /* Charts.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Charts.debug.xcconfig; sourceTree = ""; }; + 1C743FEF39ED1DAC0D7F7642E996B602 /* BarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift; sourceTree = ""; }; + 1C8C7BFEF056EC86C14852C4D2A08FF3 /* SwiftLint-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "SwiftLint-macOS.release.xcconfig"; path = "../SwiftLint-macOS/SwiftLint-macOS.release.xcconfig"; sourceTree = ""; }; + 1D286496CABCB5A7855C35586A80EB12 /* CombinedChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CombinedChartData.swift; path = Source/Charts/Data/Implementations/Standard/CombinedChartData.swift; sourceTree = ""; }; + 1F81217B1F7972C37A8BF1C464302D44 /* Charts-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Charts-Info.plist"; sourceTree = ""; }; + 22C37FA327EF8395FA55D4E88B26D1A4 /* CandleChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CandleChartData.swift; path = Source/Charts/Data/Implementations/Standard/CandleChartData.swift; sourceTree = ""; }; + 23370B0E20140BBD63087288D6A26EBA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 244CD4B7FF203C32BE80021EA21133D8 /* ComponentBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ComponentBase.swift; path = Source/Charts/Components/ComponentBase.swift; sourceTree = ""; }; + 2661A7453378228C7712D2E842C7F7E4 /* DefaultFillFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DefaultFillFormatter.swift; path = Source/Charts/Formatters/DefaultFillFormatter.swift; sourceTree = ""; }; + 2772813C4462D3FE333403E942BAFC58 /* BarHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarHighlighter.swift; path = Source/Charts/Highlight/BarHighlighter.swift; sourceTree = ""; }; + 2917D36D8E34CBE8E424867A2A6B3498 /* Pods-PiBar for iOS-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-PiBar for iOS-acknowledgements.markdown"; sourceTree = ""; }; + 2934BC0E132E9A6DD9E800B10339B639 /* XAxisRendererHorizontalBarChart.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = XAxisRendererHorizontalBarChart.swift; path = Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift; sourceTree = ""; }; + 29720EAA9D8F0EA20B34CCA01DBB8A3A /* Key.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Key.swift; path = Sources/HotKey/Key.swift; sourceTree = ""; }; + 2CB9DA008E2880512CADF7159422D8A2 /* LineChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineChartRenderer.swift; path = Source/Charts/Renderers/LineChartRenderer.swift; sourceTree = ""; }; + 2F440F9337268D36E8AB546D66225045 /* PieChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift; sourceTree = ""; }; + 2F4E1CE13C1A77ED830381DAD0172B74 /* AnimatedMoveViewJob.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedMoveViewJob.swift; path = Source/Charts/Jobs/AnimatedMoveViewJob.swift; sourceTree = ""; }; + 3294664ECF61BC3B23D88E71F409D2F5 /* MarkerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MarkerView.swift; path = Source/Charts/Components/MarkerView.swift; sourceTree = ""; }; + 32D2815154251C04CBDCAA862B8C795A /* LineRadarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineRadarChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift; sourceTree = ""; }; + 32D9BCE3CA31180EB6986D417D808265 /* DataApproximator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataApproximator.swift; path = Source/Charts/Filters/DataApproximator.swift; sourceTree = ""; }; + 33F631FAAA2CC9979B92E29A45362161 /* Pods-PiBar for macOS-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-PiBar for macOS-acknowledgements.markdown"; sourceTree = ""; }; + 34CFD73408F2B072CBAD933AEDB4A812 /* ZoomViewJob.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ZoomViewJob.swift; path = Source/Charts/Jobs/ZoomViewJob.swift; sourceTree = ""; }; + 352B53865FA675D09EA263AEC626AB09 /* ILineScatterCandleRadarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ILineScatterCandleRadarChartDataSet.swift; path = Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift; sourceTree = ""; }; + 363872CC75C12901FFEE2EF7BA365AF5 /* Pods-PiBar for iOS-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-PiBar for iOS-frameworks.sh"; sourceTree = ""; }; + 368473E61A6407A4004C227203988F08 /* HorizontalBarChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HorizontalBarChartView.swift; path = Source/Charts/Charts/HorizontalBarChartView.swift; sourceTree = ""; }; + 3921EB658010A1A98BE9C22BB83C2F3C /* TransformerHorizontalBarChart.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransformerHorizontalBarChart.swift; path = Source/Charts/Utils/TransformerHorizontalBarChart.swift; sourceTree = ""; }; + 395C5E41440F4B38B3F24BD7B1650819 /* IAxisValueFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IAxisValueFormatter.swift; path = Source/Charts/Formatters/IAxisValueFormatter.swift; sourceTree = ""; }; + 39B226E6F6386306EDE864829641C400 /* XAxis.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = XAxis.swift; path = Source/Charts/Components/XAxis.swift; sourceTree = ""; }; + 3A24BBDE802F89F581D8B87167964E4B /* AnimatedZoomViewJob.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedZoomViewJob.swift; path = Source/Charts/Jobs/AnimatedZoomViewJob.swift; sourceTree = ""; }; + 3B814EAFBC93CD71ED03508F84771A62 /* Charts-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Charts-prefix.pch"; sourceTree = ""; }; + 3D486CE56C1DCFDFACC043AA9637506F /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/AppKit.framework; sourceTree = DEVELOPER_DIR; }; + 3D96B13A0BB622CC7F2E5B57D4C83DA5 /* BubbleChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BubbleChartDataProvider.swift; path = Source/Charts/Interfaces/BubbleChartDataProvider.swift; sourceTree = ""; }; + 3F8181E0C1E9520A83B96573A045C399 /* LegendRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LegendRenderer.swift; path = Source/Charts/Renderers/LegendRenderer.swift; sourceTree = ""; }; + 40C4E105F3E9D1457850D45D2ADB198B /* XAxisRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = XAxisRenderer.swift; path = Source/Charts/Renderers/XAxisRenderer.swift; sourceTree = ""; }; + 4121874F8B899F8A18A6D42CFEABF649 /* Pods-PiBar for iOS-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-PiBar for iOS-Info.plist"; sourceTree = ""; }; + 414364F817C1844C9E38EACB1E26293E /* IBubbleChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IBubbleChartDataSet.swift; path = Source/Charts/Data/Interfaces/IBubbleChartDataSet.swift; sourceTree = ""; }; + 444F78B73604CAD35ED26ACF855F2CCD /* NSEventModifierFlags+HotKey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSEventModifierFlags+HotKey.swift"; path = "Sources/HotKey/NSEventModifierFlags+HotKey.swift"; sourceTree = ""; }; + 45D11ACD2351A43DE2C2E4F582CBD3B7 /* XShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = XShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/XShapeRenderer.swift; sourceTree = ""; }; + 4651DB1A3745D6DE10BBAAC032A11263 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/Carbon.framework; sourceTree = DEVELOPER_DIR; }; + 4711142CDB8852FDF587823F7A8D3A0A /* DefaultValueFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DefaultValueFormatter.swift; path = Source/Charts/Formatters/DefaultValueFormatter.swift; sourceTree = ""; }; + 47C93CD7E7AEE53FCAA9BFF35BAFA1EB /* Highlight.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Highlight.swift; path = Source/Charts/Highlight/Highlight.swift; sourceTree = ""; }; + 48207F38EACD2A5AADE370A4ACDB04E3 /* IBarLineScatterCandleBubbleChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IBarLineScatterCandleBubbleChartDataSet.swift; path = Source/Charts/Data/Interfaces/IBarLineScatterCandleBubbleChartDataSet.swift; sourceTree = ""; }; + 490C1B82D605ED351F94229382153A7F /* IHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IHighlighter.swift; path = Source/Charts/Highlight/IHighlighter.swift; sourceTree = ""; }; + 4A9C8B871EE3F4A0CDDC70BD5DBA100C /* IFillFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IFillFormatter.swift; path = Source/Charts/Formatters/IFillFormatter.swift; sourceTree = ""; }; + 4DE53E34B2E21972A0204D7A2209E3E3 /* SwiftLint-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "SwiftLint-iOS.release.xcconfig"; sourceTree = ""; }; + 4F5DCF57B389710682DCE9D8565DF8E2 /* LineChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift; sourceTree = ""; }; + 4FC1890CFB0B6485DD3C43DEBACD8FF8 /* IValueFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IValueFormatter.swift; path = Source/Charts/Formatters/IValueFormatter.swift; sourceTree = ""; }; + 52A43672B987BF172D047574BBC71FF0 /* Platform+Touch Handling.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Platform+Touch Handling.swift"; path = "Source/Charts/Utils/Platform+Touch Handling.swift"; sourceTree = ""; }; + 5556E1C487AEE536DF5EE084D4C99330 /* CandleStickChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CandleStickChartView.swift; path = Source/Charts/Charts/CandleStickChartView.swift; sourceTree = ""; }; + 5635AFFDCA66E46FC969397D6C491F60 /* IShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/IShapeRenderer.swift; sourceTree = ""; }; + 593578BB5D43FC02FD1CD89B75502607 /* CrossShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CrossShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift; sourceTree = ""; }; + 5A1F32D4A13450CE8BA678EC0C700137 /* YAxisRendererHorizontalBarChart.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = YAxisRendererHorizontalBarChart.swift; path = Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift; sourceTree = ""; }; + 5A5360DB0FF01F3EB3858F4E173280FF /* CandleChartDataEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CandleChartDataEntry.swift; path = Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift; sourceTree = ""; }; + 5AB1086EF59980430D14ECD1CFA9CDCC /* BarChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarChartDataProvider.swift; path = Source/Charts/Interfaces/BarChartDataProvider.swift; sourceTree = ""; }; + 5B5997095B5B3486DEC3BF9839A20242 /* ChartAnimationEasing.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartAnimationEasing.swift; path = Source/Charts/Animation/ChartAnimationEasing.swift; sourceTree = ""; }; + 5BA3D36C8146CCABEEA88FA761FC5FA9 /* YAxis.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = YAxis.swift; path = Source/Charts/Components/YAxis.swift; sourceTree = ""; }; + 5BC98BCCB70B4149654FCDFFEA08761B /* ChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartData.swift; path = Source/Charts/Data/Implementations/Standard/ChartData.swift; sourceTree = ""; }; + 5D00E3904DC644D85307DB616AE70704 /* BubbleChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BubbleChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift; sourceTree = ""; }; + 5D783E4FB64E5B23815B174E961651CF /* Animator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Animator.swift; path = Source/Charts/Animation/Animator.swift; sourceTree = ""; }; + 5E5A18E310985E019B14966EC691669B /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; + 60032EFEF6BA80A234E3DCFFF5871937 /* PieChartDataEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieChartDataEntry.swift; path = Source/Charts/Data/Implementations/Standard/PieChartDataEntry.swift; sourceTree = ""; }; + 6204B348415B8B6070A0EE8EADCC8102 /* HotKey.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = HotKey.modulemap; sourceTree = ""; }; + 639840868C7F877C57ED23EE194D190C /* ChartLimitLine.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartLimitLine.swift; path = Source/Charts/Components/ChartLimitLine.swift; sourceTree = ""; }; + 6677816AA5273790F636847E7EB6F6FC /* PieChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieChartView.swift; path = Source/Charts/Charts/PieChartView.swift; sourceTree = ""; }; + 67AF26BDBC5C1011C84B823325516A65 /* IMarker.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IMarker.swift; path = Source/Charts/Components/IMarker.swift; sourceTree = ""; }; + 69ED2D49A9B4002A9B7D538E22277D27 /* BarLineScatterCandleBubbleChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarLineScatterCandleBubbleChartData.swift; path = Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift; sourceTree = ""; }; + 6A023BD4C2C00CB8839587D670DD14CD /* CombinedChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CombinedChartView.swift; path = Source/Charts/Charts/CombinedChartView.swift; sourceTree = ""; }; + 6E14E27F329B3F8FA97C43F34CDBC65B /* RadarHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RadarHighlighter.swift; path = Source/Charts/Highlight/RadarHighlighter.swift; sourceTree = ""; }; + 70832718C7F930DF0C0076D9B7B0D795 /* BarChartDataEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarChartDataEntry.swift; path = Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift; sourceTree = ""; }; + 711AC91C488BD504E029B6291ED7CC85 /* ChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartDataProvider.swift; path = Source/Charts/Interfaces/ChartDataProvider.swift; sourceTree = ""; }; + 7378111D9560C5E98B0B95AF0F67461B /* LineRadarRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineRadarRenderer.swift; path = Source/Charts/Renderers/LineRadarRenderer.swift; sourceTree = ""; }; + 7563D9353E9BF0AC1CC19D8CDE58CFAC /* BubbleChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BubbleChartView.swift; path = Source/Charts/Charts/BubbleChartView.swift; sourceTree = ""; }; + 7E24678C6B1E49C2024EC300DB731F51 /* Pods_PiBar_for_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PiBar_for_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 81CAD649D17F3E160C64BE89A140E986 /* ChevronDownShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChevronDownShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift; sourceTree = ""; }; + 82BF88949AD6C4860FC1A6C545B73484 /* MoveViewJob.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MoveViewJob.swift; path = Source/Charts/Jobs/MoveViewJob.swift; sourceTree = ""; }; + 83284BAFFC99DA4BDE43C336ECA3A765 /* HorizontalBarChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HorizontalBarChartRenderer.swift; path = Source/Charts/Renderers/HorizontalBarChartRenderer.swift; sourceTree = ""; }; + 871CAE8F63266EE4192FB851AA4D0C16 /* Charts.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Charts.modulemap; sourceTree = ""; }; + 875211C8A1BA177A2E62C99113C88F00 /* ScatterChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ScatterChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift; sourceTree = ""; }; + 8874EA9D56F2D25ADC0F27B429AFE971 /* Pods-PiBar for iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-PiBar for iOS.release.xcconfig"; sourceTree = ""; }; + 88D9AEFEF86395C5567A2B35A6A3ECE8 /* LineChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineChartData.swift; path = Source/Charts/Data/Implementations/Standard/LineChartData.swift; sourceTree = ""; }; + 8BBD20530C653732EFAD77F07BBC9D7E /* Description.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Description.swift; path = Source/Charts/Components/Description.swift; sourceTree = ""; }; + 8EDA33A93FDD63744055B8289AEBFA6D /* ILineChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ILineChartDataSet.swift; path = Source/Charts/Data/Interfaces/ILineChartDataSet.swift; sourceTree = ""; }; + 907823E94DFCC439CBE000E0DF34B873 /* CombinedChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CombinedChartDataProvider.swift; path = Source/Charts/Interfaces/CombinedChartDataProvider.swift; sourceTree = ""; }; + 90F689617DB535EFA72FB84EF7D6B851 /* CircleShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CircleShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift; sourceTree = ""; }; + 9151D7F121CB0A7AA14C4E1A998292A8 /* CombinedChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CombinedChartRenderer.swift; path = Source/Charts/Renderers/CombinedChartRenderer.swift; sourceTree = ""; }; + 921C052C34B3D2E3C8EDC80C9C9706BA /* Pods_PiBar_for_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PiBar_for_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 94327B674E713647F8EA01260F95CBFA /* RadarChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RadarChartRenderer.swift; path = Source/Charts/Renderers/RadarChartRenderer.swift; sourceTree = ""; }; + 9445753CB81AE0B68A4C5FFCA2C40C26 /* PieRadarHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieRadarHighlighter.swift; path = Source/Charts/Highlight/PieRadarHighlighter.swift; sourceTree = ""; }; + 94A5C32B9668A5AA30811484A9D87755 /* Platform+Graphics.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Platform+Graphics.swift"; path = "Source/Charts/Utils/Platform+Graphics.swift"; sourceTree = ""; }; + 95659CAA2E2368804ADD057DD08D53D9 /* ViewPortHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ViewPortHandler.swift; path = Source/Charts/Utils/ViewPortHandler.swift; sourceTree = ""; }; + 95BE8F0A9508E6F1CD0F8CD41402C6C1 /* SwiftLint-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "SwiftLint-iOS.debug.xcconfig"; sourceTree = ""; }; + 9802DF36DF676772550A3ADD37D8B03B /* ChartDataRendererBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartDataRendererBase.swift; path = Source/Charts/Renderers/ChartDataRendererBase.swift; sourceTree = ""; }; + 98DA53EC25C12781C638CC02D8D10934 /* ScatterChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ScatterChartDataProvider.swift; path = Source/Charts/Interfaces/ScatterChartDataProvider.swift; sourceTree = ""; }; 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - A362C7E6DCA3AA3091AAC7D820186A93 /* Pods_PiBar.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PiBar.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B7BA1985099662EC8D4524DE90B39F70 /* HotKey.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = HotKey.modulemap; sourceTree = ""; }; - C45D6A9423CD5A65CB89B7FAB8572897 /* SwiftLint.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftLint.debug.xcconfig; sourceTree = ""; }; - C9679204B9F60619EF9A77AC75DAD2A9 /* HotKey-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "HotKey-dummy.m"; sourceTree = ""; }; - CDE189E76FE5BFB347A2E8F628AF8188 /* Pods-PiBar.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-PiBar.release.xcconfig"; sourceTree = ""; }; - E0A08161AAA89131150FB117FA3FFD44 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/AppKit.framework; sourceTree = DEVELOPER_DIR; }; - E0B1534582A88AC85E5869A791797730 /* Pods-PiBar-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-PiBar-umbrella.h"; sourceTree = ""; }; - E2CD43DAA14458EE1E23C193BD29E52D /* SwiftLint.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftLint.release.xcconfig; sourceTree = ""; }; - EEA77483581310D677457C751AE34FE4 /* Key.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Key.swift; path = Sources/HotKey/Key.swift; sourceTree = ""; }; + A16AFDF3BFC323B14A3D741F03C6649B /* Renderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Renderer.swift; path = Source/Charts/Renderers/Renderer.swift; sourceTree = ""; }; + A1C1B977ED8804E8AEEC884E7359EE58 /* Charts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Charts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A28B259EF5E09B69DB6A976AED8FB69D /* LineChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineChartDataProvider.swift; path = Source/Charts/Interfaces/LineChartDataProvider.swift; sourceTree = ""; }; + A46F5F9E574E9C142D41BBEEA183FD38 /* MarkerImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MarkerImage.swift; path = Source/Charts/Components/MarkerImage.swift; sourceTree = ""; }; + A613E745688FF02C525D10E8768C8471 /* RadarChartDataEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RadarChartDataEntry.swift; path = Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift; sourceTree = ""; }; + AA0DA4BB9F332363E6A51E6D8B7632AE /* BarLineScatterCandleBubbleChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarLineScatterCandleBubbleChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift; sourceTree = ""; }; + AA83EE25C715BDC7405BA5BABBA98CF3 /* PieChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieChartData.swift; path = Source/Charts/Data/Implementations/Standard/PieChartData.swift; sourceTree = ""; }; + AAD9C7FE782710481AB2BFE38DB1C959 /* LegendEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LegendEntry.swift; path = Source/Charts/Components/LegendEntry.swift; sourceTree = ""; }; + AB1FD5EC58DABC62B263392FBC987A32 /* PieChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieChartRenderer.swift; path = Source/Charts/Renderers/PieChartRenderer.swift; sourceTree = ""; }; + AF5EB703D9C96F90868593E905D7AD02 /* DataApproximator+N.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DataApproximator+N.swift"; path = "Source/Charts/Filters/DataApproximator+N.swift"; sourceTree = ""; }; + AF7C4C3115D6C19C96BEBD5845AC9EE7 /* Fill.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Fill.swift; path = Source/Charts/Utils/Fill.swift; sourceTree = ""; }; + AFE6A24B81FDB4ABAB0C803A02370576 /* ChartUtils.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartUtils.swift; path = Source/Charts/Utils/ChartUtils.swift; sourceTree = ""; }; + B0BB45D92B9F775E0B42BF683A95F5E1 /* HotKey-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "HotKey-Info.plist"; sourceTree = ""; }; + B57284B3CA2A39445B9854516C306774 /* CandleChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CandleChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift; sourceTree = ""; }; + B933C303CE788551510DD2D56A4E6D13 /* BarLineScatterCandleBubbleRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarLineScatterCandleBubbleRenderer.swift; path = Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift; sourceTree = ""; }; + BBB24E722A836171624CF75AE537803E /* Pods-PiBar for macOS-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-PiBar for macOS-Info.plist"; sourceTree = ""; }; + BC6DBF8160FC08BFD2455A8052AA94B7 /* IRadarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IRadarChartDataSet.swift; path = Source/Charts/Data/Interfaces/IRadarChartDataSet.swift; sourceTree = ""; }; + BCF444F89EF887198F973A22E19B0604 /* HotKey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HotKey.swift; path = Sources/HotKey/HotKey.swift; sourceTree = ""; }; + BCF494FFFCCCCE678E0E5F6EEF696028 /* HotKey-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "HotKey-prefix.pch"; sourceTree = ""; }; + BD360DE8DD14870AB4A85343429BD5DE /* HotKey-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "HotKey-dummy.m"; sourceTree = ""; }; + BD40E15BBF8D4A43E0EF41542A297D9D /* HotKeysController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HotKeysController.swift; path = Sources/HotKey/HotKeysController.swift; sourceTree = ""; }; + BE2E4EED98013725C32276E547328091 /* BubbleChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BubbleChartRenderer.swift; path = Source/Charts/Renderers/BubbleChartRenderer.swift; sourceTree = ""; }; + BECA3B877E8DE7A0C5A4A1B674916577 /* Pods-PiBar for macOS.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-PiBar for macOS.modulemap"; sourceTree = ""; }; + C0F4EDBCCFB5FCA58A057FE69FACE3A5 /* Pods-PiBar for macOS-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-PiBar for macOS-dummy.m"; sourceTree = ""; }; + C2C02E3862AE6E4C1DCC69993DF57FFD /* Platform+Gestures.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Platform+Gestures.swift"; path = "Source/Charts/Utils/Platform+Gestures.swift"; sourceTree = ""; }; + C2DAB85F670221A85ADDCF9EF6A2CE8A /* IPieChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IPieChartDataSet.swift; path = Source/Charts/Data/Interfaces/IPieChartDataSet.swift; sourceTree = ""; }; + C3523B89A99586703D14801648519131 /* ChartHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartHighlighter.swift; path = Source/Charts/Highlight/ChartHighlighter.swift; sourceTree = ""; }; + C4064E7B401FA5A37E039D20EC0A78C0 /* RadarChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RadarChartView.swift; path = Source/Charts/Charts/RadarChartView.swift; sourceTree = ""; }; + C42C2D79C8678FAB439F11F69E03E4FB /* ScatterChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ScatterChartData.swift; path = Source/Charts/Data/Implementations/Standard/ScatterChartData.swift; sourceTree = ""; }; + C4C864721875A16D2FDC517AF11C2C14 /* Legend.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Legend.swift; path = Source/Charts/Components/Legend.swift; sourceTree = ""; }; + C52B32CB317EFE6C2877A92F19356421 /* Pods-PiBar for macOS-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-PiBar for macOS-acknowledgements.plist"; sourceTree = ""; }; + C5C2876FFF1E2A2C89AD7BAF3849EAF3 /* Platform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Platform.swift; path = Source/Charts/Utils/Platform.swift; sourceTree = ""; }; + C7474834432C57BA0DCB62390276C8F3 /* Pods-PiBar for iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-PiBar for iOS.debug.xcconfig"; sourceTree = ""; }; + C7B30F113D09D8675C4E2BC58753A160 /* RadarChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RadarChartData.swift; path = Source/Charts/Data/Implementations/Standard/RadarChartData.swift; sourceTree = ""; }; + C8F797AE00C211197E2F8A9C08654968 /* CandleStickChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CandleStickChartRenderer.swift; path = Source/Charts/Renderers/CandleStickChartRenderer.swift; sourceTree = ""; }; + CC2506537E1D9F83506E6E877AA1A869 /* ViewPortJob.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ViewPortJob.swift; path = Source/Charts/Jobs/ViewPortJob.swift; sourceTree = ""; }; + CD2733644CE726BB60AB6E7E43B857B0 /* CandleChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CandleChartDataProvider.swift; path = Source/Charts/Interfaces/CandleChartDataProvider.swift; sourceTree = ""; }; + CD4F49A642CFCE1F3E2D0D17F1B302EF /* Pods-PiBar for iOS-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-PiBar for iOS-acknowledgements.plist"; sourceTree = ""; }; + CDF734830016E3088136D5243F547F5D /* HotKey.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = HotKey.debug.xcconfig; sourceTree = ""; }; + CE92380CA74FDE527DC33A8DD1281DE3 /* Range.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Range.swift; path = Source/Charts/Highlight/Range.swift; sourceTree = ""; }; + CFD17F9DE444E07260998B08AC164B82 /* ILineRadarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ILineRadarChartDataSet.swift; path = Source/Charts/Data/Interfaces/ILineRadarChartDataSet.swift; sourceTree = ""; }; + D1AE3079B10A1A511DBC474F7048B98E /* ScatterChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ScatterChartRenderer.swift; path = Source/Charts/Renderers/ScatterChartRenderer.swift; sourceTree = ""; }; + D200D11F29B73A83E32A3482E89EF5C3 /* ChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/ChartDataSet.swift; sourceTree = ""; }; + D22C34DDE627B4250229FD45B6BB6511 /* HotKey-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "HotKey-umbrella.h"; sourceTree = ""; }; + D2B2134DEB4D8B303CF159710CC430A5 /* LineScatterCandleRadarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineScatterCandleRadarChartDataSet.swift; path = Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift; sourceTree = ""; }; + D8132C4EA18AE50A4770D3FECF791AFA /* LineChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LineChartView.swift; path = Source/Charts/Charts/LineChartView.swift; sourceTree = ""; }; + D94CEE0307835B9074C934FF28069FA9 /* IBarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IBarChartDataSet.swift; path = Source/Charts/Data/Interfaces/IBarChartDataSet.swift; sourceTree = ""; }; + DA2C4115A525E3C81D8EBEA8B798B98D /* BarLineChartViewBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarLineChartViewBase.swift; path = Source/Charts/Charts/BarLineChartViewBase.swift; sourceTree = ""; }; + DB3547D8AACEFE6671353ACE26F88A3A /* Pods-PiBar for macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-PiBar for macOS.debug.xcconfig"; sourceTree = ""; }; + E071CDADFA60D6619D14E6880D074D6F /* BubbleChartDataEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BubbleChartDataEntry.swift; path = Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift; sourceTree = ""; }; + E100506A7D371448771162249393D1D1 /* ChartDataEntryBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartDataEntryBase.swift; path = Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift; sourceTree = ""; }; + E2043E61F9E8D76F3F95A3DA93FC9DDA /* DefaultAxisValueFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DefaultAxisValueFormatter.swift; path = Source/Charts/Formatters/DefaultAxisValueFormatter.swift; sourceTree = ""; }; + E309C4D380DECA006E761B15CD8FBF99 /* ChevronUpShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChevronUpShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift; sourceTree = ""; }; + E588899BACBAF52907A541B038CC5627 /* Pods-PiBar for iOS.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-PiBar for iOS.modulemap"; sourceTree = ""; }; + E5BF7686C4F3C30198A4CD9FA008367E /* IChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IChartDataSet.swift; path = Source/Charts/Data/Interfaces/IChartDataSet.swift; sourceTree = ""; }; + E68CC3445CA2353DD389F1C888C973E4 /* XAxisRendererRadarChart.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = XAxisRendererRadarChart.swift; path = Source/Charts/Renderers/XAxisRendererRadarChart.swift; sourceTree = ""; }; + E68DDBEF0B4A92803CA6B07280F6B1B7 /* Platform+Accessibility.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Platform+Accessibility.swift"; path = "Source/Charts/Utils/Platform+Accessibility.swift"; sourceTree = ""; }; + E70F95A461C1D95565C17F1799B5B13D /* PieHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieHighlighter.swift; path = Source/Charts/Highlight/PieHighlighter.swift; sourceTree = ""; }; + E78998534AC40C7E72F90ACE6493A635 /* BarLineScatterCandleBubbleChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarLineScatterCandleBubbleChartDataProvider.swift; path = Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift; sourceTree = ""; }; + E941D8EBB13F56A36AC17461EB7F12F2 /* SquareShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SquareShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift; sourceTree = ""; }; + EA9EDF9643C07C7796A43915A3759062 /* BubbleChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BubbleChartData.swift; path = Source/Charts/Data/Implementations/Standard/BubbleChartData.swift; sourceTree = ""; }; + EE5B3CB181C3DDDDC24C0AFF87FAA8A9 /* SwiftLint-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "SwiftLint-macOS.debug.xcconfig"; path = "../SwiftLint-macOS/SwiftLint-macOS.debug.xcconfig"; sourceTree = ""; }; + F04AC0522545DFF6FF42E62E0A4D4B2D /* AxisRendererBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AxisRendererBase.swift; path = Source/Charts/Renderers/AxisRendererBase.swift; sourceTree = ""; }; + F058BBA8A2B5559901468CC2B693EA26 /* Pods-PiBar for macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-PiBar for macOS.release.xcconfig"; sourceTree = ""; }; + F066DE17AF257744F13593C11AB3B414 /* BarChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarChartRenderer.swift; path = Source/Charts/Renderers/BarChartRenderer.swift; sourceTree = ""; }; + F1091D499CF4038119CC9CD9E9E14A98 /* TriangleShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TriangleShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift; sourceTree = ""; }; + F31EFCCC9A4CA02BC3D8C8B9C1A90346 /* BarChartData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarChartData.swift; path = Source/Charts/Data/Implementations/Standard/BarChartData.swift; sourceTree = ""; }; F36B2E7BB4C3E92E4DF7443FAFF3AE3F /* HotKey.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HotKey.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - FAC1F45CCB8C15294E87E77FEA979581 /* Pods-PiBar.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-PiBar.debug.xcconfig"; sourceTree = ""; }; - FEE6DE741C3E6CB6301400B27B46AF00 /* HotKey-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "HotKey-prefix.pch"; sourceTree = ""; }; - FFBC9A90999F22CE48B198862DFC5B5D /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Carbon.framework; sourceTree = DEVELOPER_DIR; }; + F9A1E4D59B5E97262EBD7772CB794554 /* ChartBaseDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartBaseDataSet.swift; path = Source/Charts/Data/Implementations/ChartBaseDataSet.swift; sourceTree = ""; }; + FBFE3526033234DC9BC0B3FE05F43A94 /* Pods-PiBar for macOS-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-PiBar for macOS-umbrella.h"; sourceTree = ""; }; + FD6FF5886BFE76D65CBC5AFB034D6B7F /* ChartDataEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChartDataEntry.swift; path = Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift; sourceTree = ""; }; + FEEA3878A4F44FE2E54F0DC0C49919E5 /* BarChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarChartView.swift; path = Source/Charts/Charts/BarChartView.swift; sourceTree = ""; }; + FF77D965BBBC24829165C077469F2888 /* HorizontalBarHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HorizontalBarHighlighter.swift; path = Source/Charts/Highlight/HorizontalBarHighlighter.swift; sourceTree = ""; }; + FF7B8B0029F1732FC48755E3C038CFCD /* CombinedHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CombinedHighlighter.swift; path = Source/Charts/Highlight/CombinedHighlighter.swift; sourceTree = ""; }; + FF9645910EDC22A164E994394CCAFB4C /* PieRadarChartViewBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieRadarChartViewBase.swift; path = Source/Charts/Charts/PieRadarChartViewBase.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - AC925B3BC8F8597F439F47602934FE1D /* Frameworks */ = { + 0109DF9091F5396F6C628BAE0A6F7CE6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A7E344DB25C4335481A9FE224A1C47E9 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7E4835E1A446981AFA38D55A1D9E0142 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2268DE11251696977088EB5D4310171E /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB1B84B641C42EBDAA1B4B790B06A48 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - BB3AF94A65AEC246F6A55EF4C26B0C15 /* Cocoa.framework in Frameworks */, + ED652BAC315BB6FDDEB8C02789ED3D3D /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -97,141 +444,360 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5AC7E7B8F7316B6F468E3A7919954BFB /* AppKit.framework in Frameworks */, - F9FBEA632006006292C99342FF7CC2C9 /* Carbon.framework in Frameworks */, - C0E9E705DDA754D6E1EC52A050650B70 /* Cocoa.framework in Frameworks */, + 3BDFA459949B787FF4BA39DE9BF523AB /* AppKit.framework in Frameworks */, + 9BE82DE86F384EAD7B0B42894C0ECD75 /* Carbon.framework in Frameworks */, + CDFF1CFDBA33AF524DA0CDA8C338B44A /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 23A5D522D1275959966E7848A1E5D2F8 /* SwiftLint */ = { + 0CEC211773933FBBBB0D368EF155BF9E /* SwiftLint */ = { isa = PBXGroup; children = ( - 958749A3BA8E2558B8F215BA5709B7A4 /* Support Files */, + CF1734A99F7A041F5A5AA46EF3E79006 /* Support Files */, ); path = SwiftLint; sourceTree = ""; }; - 2E143565E9FB4296D543F043709F30CD /* Targets Support Files */ = { + 19CE0A6B4F7B271FF57FAC15078A6C65 /* Support Files */ = { isa = PBXGroup; children = ( - 73A089C2473AC13F896B4AE663481768 /* Pods-PiBar */, + 871CAE8F63266EE4192FB851AA4D0C16 /* Charts.modulemap */, + 0D07A24C25ECC0790372BBD1AC71BAB2 /* Charts-dummy.m */, + 1F81217B1F7972C37A8BF1C464302D44 /* Charts-Info.plist */, + 3B814EAFBC93CD71ED03508F84771A62 /* Charts-prefix.pch */, + 0837BF2D2874FB3203FC7EDA183F9012 /* Charts-umbrella.h */, + 1C699EB5CCB69D147DDF43F493B1D1D6 /* Charts.debug.xcconfig */, + 0DCE4277E1341B290A8E556C5C2B19FC /* Charts.release.xcconfig */, ); - name = "Targets Support Files"; + name = "Support Files"; + path = "../Target Support Files/Charts"; sourceTree = ""; }; - 73A089C2473AC13F896B4AE663481768 /* Pods-PiBar */ = { + 1B8B7BF1E1C4B80F351CB68EA70CC90A /* HotKey */ = { isa = PBXGroup; children = ( - 7590F3CB1E12178036147CF9EE2744DF /* Pods-PiBar.modulemap */, - 716355C89B577E00268BBCDDABDD6654 /* Pods-PiBar-acknowledgements.markdown */, - 5A2E111A2B15CDC74756281684BD9272 /* Pods-PiBar-acknowledgements.plist */, - 4F2A08F3DB0E5A7EB3AC5560E0B4B721 /* Pods-PiBar-dummy.m */, - 3C1FE6D309736C69BAF6F20B1F52D9BF /* Pods-PiBar-frameworks.sh */, - 93AE2864FC4C28D40DE2C9BDF459C9F2 /* Pods-PiBar-Info.plist */, - E0B1534582A88AC85E5869A791797730 /* Pods-PiBar-umbrella.h */, - FAC1F45CCB8C15294E87E77FEA979581 /* Pods-PiBar.debug.xcconfig */, - CDE189E76FE5BFB347A2E8F628AF8188 /* Pods-PiBar.release.xcconfig */, - ); - name = "Pods-PiBar"; - path = "Target Support Files/Pods-PiBar"; + BCF444F89EF887198F973A22E19B0604 /* HotKey.swift */, + BD40E15BBF8D4A43E0EF41542A297D9D /* HotKeysController.swift */, + 29720EAA9D8F0EA20B34CCA01DBB8A3A /* Key.swift */, + 0196FF4461C772C4FD219EAE800A0EA5 /* KeyCombo.swift */, + 444F78B73604CAD35ED26ACF855F2CCD /* NSEventModifierFlags+HotKey.swift */, + 69A7CA0E3D910F6A07333D7304A3E74D /* Support Files */, + ); + path = HotKey; sourceTree = ""; }; - 867952DE18004326E1A758FDBA0A19DC /* OS X */ = { + 29190C97F3C2566104C3A69CD9029F70 /* Pods-PiBar for iOS */ = { isa = PBXGroup; children = ( - E0A08161AAA89131150FB117FA3FFD44 /* AppKit.framework */, - FFBC9A90999F22CE48B198862DFC5B5D /* Carbon.framework */, - 123D6DC4B5EFB570985CEFACFC1B93CE /* Cocoa.framework */, + E588899BACBAF52907A541B038CC5627 /* Pods-PiBar for iOS.modulemap */, + 2917D36D8E34CBE8E424867A2A6B3498 /* Pods-PiBar for iOS-acknowledgements.markdown */, + CD4F49A642CFCE1F3E2D0D17F1B302EF /* Pods-PiBar for iOS-acknowledgements.plist */, + 1A763B8F2ADB3B165DDA2FF6BBBA8E29 /* Pods-PiBar for iOS-dummy.m */, + 363872CC75C12901FFEE2EF7BA365AF5 /* Pods-PiBar for iOS-frameworks.sh */, + 4121874F8B899F8A18A6D42CFEABF649 /* Pods-PiBar for iOS-Info.plist */, + 0804594DEA4D52747B3B985663B23B48 /* Pods-PiBar for iOS-umbrella.h */, + C7474834432C57BA0DCB62390276C8F3 /* Pods-PiBar for iOS.debug.xcconfig */, + 8874EA9D56F2D25ADC0F27B429AFE971 /* Pods-PiBar for iOS.release.xcconfig */, ); - name = "OS X"; + name = "Pods-PiBar for iOS"; + path = "Target Support Files/Pods-PiBar for iOS"; + sourceTree = ""; + }; + 68E3A320B1E556F97AE69F7FD8649024 /* Pods */ = { + isa = PBXGroup; + children = ( + C252A709525F3EAC4C352190F5D3ABFF /* Charts */, + 1B8B7BF1E1C4B80F351CB68EA70CC90A /* HotKey */, + 0CEC211773933FBBBB0D368EF155BF9E /* SwiftLint */, + ); + name = Pods; sourceTree = ""; }; - 958749A3BA8E2558B8F215BA5709B7A4 /* Support Files */ = { + 69A7CA0E3D910F6A07333D7304A3E74D /* Support Files */ = { isa = PBXGroup; children = ( - C45D6A9423CD5A65CB89B7FAB8572897 /* SwiftLint.debug.xcconfig */, - E2CD43DAA14458EE1E23C193BD29E52D /* SwiftLint.release.xcconfig */, + 6204B348415B8B6070A0EE8EADCC8102 /* HotKey.modulemap */, + BD360DE8DD14870AB4A85343429BD5DE /* HotKey-dummy.m */, + B0BB45D92B9F775E0B42BF683A95F5E1 /* HotKey-Info.plist */, + BCF494FFFCCCCE678E0E5F6EEF696028 /* HotKey-prefix.pch */, + D22C34DDE627B4250229FD45B6BB6511 /* HotKey-umbrella.h */, + CDF734830016E3088136D5243F547F5D /* HotKey.debug.xcconfig */, + 09CFF440A13A62AE14A558484BC4B5D0 /* HotKey.release.xcconfig */, ); name = "Support Files"; - path = "../Target Support Files/SwiftLint"; + path = "../Target Support Files/HotKey"; sourceTree = ""; }; - 9964B96653A59F2D338403CB3AFF245A /* Products */ = { + 6F979098037E24B68A6871AA6574AA60 /* Products */ = { isa = PBXGroup; children = ( + A1C1B977ED8804E8AEEC884E7359EE58 /* Charts.framework */, F36B2E7BB4C3E92E4DF7443FAFF3AE3F /* HotKey.framework */, - A362C7E6DCA3AA3091AAC7D820186A93 /* Pods_PiBar.framework */, + 921C052C34B3D2E3C8EDC80C9C9706BA /* Pods_PiBar_for_iOS.framework */, + 7E24678C6B1E49C2024EC300DB731F51 /* Pods_PiBar_for_macOS.framework */, ); name = Products; sourceTree = ""; }; - A3CDB250FFEFB42CA2C68F1FBD92997D /* Frameworks */ = { + 7E54D7432230683E9A1763231A248317 /* Targets Support Files */ = { isa = PBXGroup; children = ( - 867952DE18004326E1A758FDBA0A19DC /* OS X */, + 29190C97F3C2566104C3A69CD9029F70 /* Pods-PiBar for iOS */, + CEAD8B8DB99B4E7119F497BCC07A8DC1 /* Pods-PiBar for macOS */, ); - name = Frameworks; + name = "Targets Support Files"; sourceTree = ""; }; - A773AD3680014F234FE336F605154904 /* HotKey */ = { + B88CE38405696D2C0153B9302B7B091D /* Core */ = { isa = PBXGroup; children = ( - 7173204885C9B0546569E846A3DDAF6F /* HotKey.swift */, - 67FDD6B68A3C6BEFE01D5859A9C46AB5 /* HotKeysController.swift */, - EEA77483581310D677457C751AE34FE4 /* Key.swift */, - 9079B716F46D611EBFE1541810454743 /* KeyCombo.swift */, - 9B791A837FA788207F296E05F0352D37 /* NSEventModifierFlags+HotKey.swift */, - D8A765A5286F84BD5EF804A80BE6CDFB /* Support Files */, + 2F4E1CE13C1A77ED830381DAD0172B74 /* AnimatedMoveViewJob.swift */, + 0F29D2077DC8B40B0C8C7273B3D8C402 /* AnimatedViewPortJob.swift */, + 3A24BBDE802F89F581D8B87167964E4B /* AnimatedZoomViewJob.swift */, + 5D783E4FB64E5B23815B174E961651CF /* Animator.swift */, + 167044BFFBE9F6C5497AA6B4CC814953 /* AxisBase.swift */, + F04AC0522545DFF6FF42E62E0A4D4B2D /* AxisRendererBase.swift */, + F31EFCCC9A4CA02BC3D8C8B9C1A90346 /* BarChartData.swift */, + 70832718C7F930DF0C0076D9B7B0D795 /* BarChartDataEntry.swift */, + 5AB1086EF59980430D14ECD1CFA9CDCC /* BarChartDataProvider.swift */, + 1C743FEF39ED1DAC0D7F7642E996B602 /* BarChartDataSet.swift */, + F066DE17AF257744F13593C11AB3B414 /* BarChartRenderer.swift */, + FEEA3878A4F44FE2E54F0DC0C49919E5 /* BarChartView.swift */, + 2772813C4462D3FE333403E942BAFC58 /* BarHighlighter.swift */, + DA2C4115A525E3C81D8EBEA8B798B98D /* BarLineChartViewBase.swift */, + 69ED2D49A9B4002A9B7D538E22277D27 /* BarLineScatterCandleBubbleChartData.swift */, + E78998534AC40C7E72F90ACE6493A635 /* BarLineScatterCandleBubbleChartDataProvider.swift */, + AA0DA4BB9F332363E6A51E6D8B7632AE /* BarLineScatterCandleBubbleChartDataSet.swift */, + B933C303CE788551510DD2D56A4E6D13 /* BarLineScatterCandleBubbleRenderer.swift */, + EA9EDF9643C07C7796A43915A3759062 /* BubbleChartData.swift */, + E071CDADFA60D6619D14E6880D074D6F /* BubbleChartDataEntry.swift */, + 3D96B13A0BB622CC7F2E5B57D4C83DA5 /* BubbleChartDataProvider.swift */, + 5D00E3904DC644D85307DB616AE70704 /* BubbleChartDataSet.swift */, + BE2E4EED98013725C32276E547328091 /* BubbleChartRenderer.swift */, + 7563D9353E9BF0AC1CC19D8CDE58CFAC /* BubbleChartView.swift */, + 22C37FA327EF8395FA55D4E88B26D1A4 /* CandleChartData.swift */, + 5A5360DB0FF01F3EB3858F4E173280FF /* CandleChartDataEntry.swift */, + CD2733644CE726BB60AB6E7E43B857B0 /* CandleChartDataProvider.swift */, + B57284B3CA2A39445B9854516C306774 /* CandleChartDataSet.swift */, + C8F797AE00C211197E2F8A9C08654968 /* CandleStickChartRenderer.swift */, + 5556E1C487AEE536DF5EE084D4C99330 /* CandleStickChartView.swift */, + 5B5997095B5B3486DEC3BF9839A20242 /* ChartAnimationEasing.swift */, + F9A1E4D59B5E97262EBD7772CB794554 /* ChartBaseDataSet.swift */, + 0557E250A1067B869EC2ED6273F65A28 /* ChartColorTemplates.swift */, + 5BC98BCCB70B4149654FCDFFEA08761B /* ChartData.swift */, + FD6FF5886BFE76D65CBC5AFB034D6B7F /* ChartDataEntry.swift */, + E100506A7D371448771162249393D1D1 /* ChartDataEntryBase.swift */, + 711AC91C488BD504E029B6291ED7CC85 /* ChartDataProvider.swift */, + 9802DF36DF676772550A3ADD37D8B03B /* ChartDataRendererBase.swift */, + D200D11F29B73A83E32A3482E89EF5C3 /* ChartDataSet.swift */, + C3523B89A99586703D14801648519131 /* ChartHighlighter.swift */, + 639840868C7F877C57ED23EE194D190C /* ChartLimitLine.swift */, + AFE6A24B81FDB4ABAB0C803A02370576 /* ChartUtils.swift */, + 0615B23DDBFBB6687E057CDF87DE4032 /* ChartViewBase.swift */, + 81CAD649D17F3E160C64BE89A140E986 /* ChevronDownShapeRenderer.swift */, + E309C4D380DECA006E761B15CD8FBF99 /* ChevronUpShapeRenderer.swift */, + 90F689617DB535EFA72FB84EF7D6B851 /* CircleShapeRenderer.swift */, + 1D286496CABCB5A7855C35586A80EB12 /* CombinedChartData.swift */, + 907823E94DFCC439CBE000E0DF34B873 /* CombinedChartDataProvider.swift */, + 9151D7F121CB0A7AA14C4E1A998292A8 /* CombinedChartRenderer.swift */, + 6A023BD4C2C00CB8839587D670DD14CD /* CombinedChartView.swift */, + FF7B8B0029F1732FC48755E3C038CFCD /* CombinedHighlighter.swift */, + 244CD4B7FF203C32BE80021EA21133D8 /* ComponentBase.swift */, + 593578BB5D43FC02FD1CD89B75502607 /* CrossShapeRenderer.swift */, + 32D9BCE3CA31180EB6986D417D808265 /* DataApproximator.swift */, + AF5EB703D9C96F90868593E905D7AD02 /* DataApproximator+N.swift */, + E2043E61F9E8D76F3F95A3DA93FC9DDA /* DefaultAxisValueFormatter.swift */, + 2661A7453378228C7712D2E842C7F7E4 /* DefaultFillFormatter.swift */, + 4711142CDB8852FDF587823F7A8D3A0A /* DefaultValueFormatter.swift */, + 8BBD20530C653732EFAD77F07BBC9D7E /* Description.swift */, + AF7C4C3115D6C19C96BEBD5845AC9EE7 /* Fill.swift */, + 47C93CD7E7AEE53FCAA9BFF35BAFA1EB /* Highlight.swift */, + 83284BAFFC99DA4BDE43C336ECA3A765 /* HorizontalBarChartRenderer.swift */, + 368473E61A6407A4004C227203988F08 /* HorizontalBarChartView.swift */, + FF77D965BBBC24829165C077469F2888 /* HorizontalBarHighlighter.swift */, + 395C5E41440F4B38B3F24BD7B1650819 /* IAxisValueFormatter.swift */, + D94CEE0307835B9074C934FF28069FA9 /* IBarChartDataSet.swift */, + 48207F38EACD2A5AADE370A4ACDB04E3 /* IBarLineScatterCandleBubbleChartDataSet.swift */, + 414364F817C1844C9E38EACB1E26293E /* IBubbleChartDataSet.swift */, + 0C789B0920F0049AB6FD589B0D61268E /* ICandleChartDataSet.swift */, + E5BF7686C4F3C30198A4CD9FA008367E /* IChartDataSet.swift */, + 4A9C8B871EE3F4A0CDDC70BD5DBA100C /* IFillFormatter.swift */, + 490C1B82D605ED351F94229382153A7F /* IHighlighter.swift */, + 8EDA33A93FDD63744055B8289AEBFA6D /* ILineChartDataSet.swift */, + CFD17F9DE444E07260998B08AC164B82 /* ILineRadarChartDataSet.swift */, + 352B53865FA675D09EA263AEC626AB09 /* ILineScatterCandleRadarChartDataSet.swift */, + 67AF26BDBC5C1011C84B823325516A65 /* IMarker.swift */, + 1755627B9AD697B1A949B2B8F4FBABA3 /* IndexAxisValueFormatter.swift */, + C2DAB85F670221A85ADDCF9EF6A2CE8A /* IPieChartDataSet.swift */, + BC6DBF8160FC08BFD2455A8052AA94B7 /* IRadarChartDataSet.swift */, + 145435D9A044342F696BBCE5962DBC6D /* IScatterChartDataSet.swift */, + 5635AFFDCA66E46FC969397D6C491F60 /* IShapeRenderer.swift */, + 4FC1890CFB0B6485DD3C43DEBACD8FF8 /* IValueFormatter.swift */, + C4C864721875A16D2FDC517AF11C2C14 /* Legend.swift */, + AAD9C7FE782710481AB2BFE38DB1C959 /* LegendEntry.swift */, + 3F8181E0C1E9520A83B96573A045C399 /* LegendRenderer.swift */, + 88D9AEFEF86395C5567A2B35A6A3ECE8 /* LineChartData.swift */, + A28B259EF5E09B69DB6A976AED8FB69D /* LineChartDataProvider.swift */, + 4F5DCF57B389710682DCE9D8565DF8E2 /* LineChartDataSet.swift */, + 2CB9DA008E2880512CADF7159422D8A2 /* LineChartRenderer.swift */, + D8132C4EA18AE50A4770D3FECF791AFA /* LineChartView.swift */, + 32D2815154251C04CBDCAA862B8C795A /* LineRadarChartDataSet.swift */, + 7378111D9560C5E98B0B95AF0F67461B /* LineRadarRenderer.swift */, + D2B2134DEB4D8B303CF159710CC430A5 /* LineScatterCandleRadarChartDataSet.swift */, + 08327D3E630948798A6E2BA498EBCD67 /* LineScatterCandleRadarRenderer.swift */, + A46F5F9E574E9C142D41BBEEA183FD38 /* MarkerImage.swift */, + 3294664ECF61BC3B23D88E71F409D2F5 /* MarkerView.swift */, + 82BF88949AD6C4860FC1A6C545B73484 /* MoveViewJob.swift */, + AA83EE25C715BDC7405BA5BABBA98CF3 /* PieChartData.swift */, + 60032EFEF6BA80A234E3DCFFF5871937 /* PieChartDataEntry.swift */, + 2F440F9337268D36E8AB546D66225045 /* PieChartDataSet.swift */, + AB1FD5EC58DABC62B263392FBC987A32 /* PieChartRenderer.swift */, + 6677816AA5273790F636847E7EB6F6FC /* PieChartView.swift */, + E70F95A461C1D95565C17F1799B5B13D /* PieHighlighter.swift */, + FF9645910EDC22A164E994394CCAFB4C /* PieRadarChartViewBase.swift */, + 9445753CB81AE0B68A4C5FFCA2C40C26 /* PieRadarHighlighter.swift */, + C5C2876FFF1E2A2C89AD7BAF3849EAF3 /* Platform.swift */, + E68DDBEF0B4A92803CA6B07280F6B1B7 /* Platform+Accessibility.swift */, + 005B336D0EA7C413B24A9172649E6BD7 /* Platform+Color.swift */, + C2C02E3862AE6E4C1DCC69993DF57FFD /* Platform+Gestures.swift */, + 94A5C32B9668A5AA30811484A9D87755 /* Platform+Graphics.swift */, + 52A43672B987BF172D047574BBC71FF0 /* Platform+Touch Handling.swift */, + C7B30F113D09D8675C4E2BC58753A160 /* RadarChartData.swift */, + A613E745688FF02C525D10E8768C8471 /* RadarChartDataEntry.swift */, + 09667EA54576A5F100E3D33F233BAEAA /* RadarChartDataSet.swift */, + 94327B674E713647F8EA01260F95CBFA /* RadarChartRenderer.swift */, + C4064E7B401FA5A37E039D20EC0A78C0 /* RadarChartView.swift */, + 6E14E27F329B3F8FA97C43F34CDBC65B /* RadarHighlighter.swift */, + CE92380CA74FDE527DC33A8DD1281DE3 /* Range.swift */, + A16AFDF3BFC323B14A3D741F03C6649B /* Renderer.swift */, + C42C2D79C8678FAB439F11F69E03E4FB /* ScatterChartData.swift */, + 98DA53EC25C12781C638CC02D8D10934 /* ScatterChartDataProvider.swift */, + 875211C8A1BA177A2E62C99113C88F00 /* ScatterChartDataSet.swift */, + D1AE3079B10A1A511DBC474F7048B98E /* ScatterChartRenderer.swift */, + 05851806E9663A3EBD3753C7BA948EFF /* ScatterChartView.swift */, + E941D8EBB13F56A36AC17461EB7F12F2 /* SquareShapeRenderer.swift */, + 11FC65C724FEDB749BAA8000308D6829 /* Transformer.swift */, + 3921EB658010A1A98BE9C22BB83C2F3C /* TransformerHorizontalBarChart.swift */, + F1091D499CF4038119CC9CD9E9E14A98 /* TriangleShapeRenderer.swift */, + 95659CAA2E2368804ADD057DD08D53D9 /* ViewPortHandler.swift */, + CC2506537E1D9F83506E6E877AA1A869 /* ViewPortJob.swift */, + 39B226E6F6386306EDE864829641C400 /* XAxis.swift */, + 40C4E105F3E9D1457850D45D2ADB198B /* XAxisRenderer.swift */, + 2934BC0E132E9A6DD9E800B10339B639 /* XAxisRendererHorizontalBarChart.swift */, + E68CC3445CA2353DD389F1C888C973E4 /* XAxisRendererRadarChart.swift */, + 45D11ACD2351A43DE2C2E4F582CBD3B7 /* XShapeRenderer.swift */, + 5BA3D36C8146CCABEEA88FA761FC5FA9 /* YAxis.swift */, + 036C87FEE09C0356FD909EB6A0ABC346 /* YAxisRenderer.swift */, + 5A1F32D4A13450CE8BA678EC0C700137 /* YAxisRendererHorizontalBarChart.swift */, + 0E3F4081A56764B8F384626813FAE68A /* YAxisRendererRadarChart.swift */, + 34CFD73408F2B072CBAD933AEDB4A812 /* ZoomViewJob.swift */, ); - path = HotKey; + name = Core; + sourceTree = ""; + }; + C252A709525F3EAC4C352190F5D3ABFF /* Charts */ = { + isa = PBXGroup; + children = ( + B88CE38405696D2C0153B9302B7B091D /* Core */, + 19CE0A6B4F7B271FF57FAC15078A6C65 /* Support Files */, + ); + path = Charts; + sourceTree = ""; + }; + CEAD8B8DB99B4E7119F497BCC07A8DC1 /* Pods-PiBar for macOS */ = { + isa = PBXGroup; + children = ( + BECA3B877E8DE7A0C5A4A1B674916577 /* Pods-PiBar for macOS.modulemap */, + 33F631FAAA2CC9979B92E29A45362161 /* Pods-PiBar for macOS-acknowledgements.markdown */, + C52B32CB317EFE6C2877A92F19356421 /* Pods-PiBar for macOS-acknowledgements.plist */, + C0F4EDBCCFB5FCA58A057FE69FACE3A5 /* Pods-PiBar for macOS-dummy.m */, + 0A1067A6A80D32F5634C0E57B31CCD60 /* Pods-PiBar for macOS-frameworks.sh */, + BBB24E722A836171624CF75AE537803E /* Pods-PiBar for macOS-Info.plist */, + FBFE3526033234DC9BC0B3FE05F43A94 /* Pods-PiBar for macOS-umbrella.h */, + DB3547D8AACEFE6671353ACE26F88A3A /* Pods-PiBar for macOS.debug.xcconfig */, + F058BBA8A2B5559901468CC2B693EA26 /* Pods-PiBar for macOS.release.xcconfig */, + ); + name = "Pods-PiBar for macOS"; + path = "Target Support Files/Pods-PiBar for macOS"; sourceTree = ""; }; CF1408CF629C7361332E53B88F7BD30C = { isa = PBXGroup; children = ( 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, - A3CDB250FFEFB42CA2C68F1FBD92997D /* Frameworks */, - E7B6D3933FA52A78D67127559530B1D3 /* Pods */, - 9964B96653A59F2D338403CB3AFF245A /* Products */, - 2E143565E9FB4296D543F043709F30CD /* Targets Support Files */, + E4A2CB193F2CAC354E3C15096484406D /* Frameworks */, + 68E3A320B1E556F97AE69F7FD8649024 /* Pods */, + 6F979098037E24B68A6871AA6574AA60 /* Products */, + 7E54D7432230683E9A1763231A248317 /* Targets Support Files */, ); sourceTree = ""; }; - D8A765A5286F84BD5EF804A80BE6CDFB /* Support Files */ = { + CF1734A99F7A041F5A5AA46EF3E79006 /* Support Files */ = { isa = PBXGroup; children = ( - B7BA1985099662EC8D4524DE90B39F70 /* HotKey.modulemap */, - C9679204B9F60619EF9A77AC75DAD2A9 /* HotKey-dummy.m */, - 73C0DC168EBF587BFB7F868747092C0C /* HotKey-Info.plist */, - FEE6DE741C3E6CB6301400B27B46AF00 /* HotKey-prefix.pch */, - 5CED4EFAB4A1A56BB51A65EDBB6CAF5E /* HotKey-umbrella.h */, - 39D6FC83A74766CE2022EE87101A6CAE /* HotKey.debug.xcconfig */, - 3189FE65632B209B3608F60B0CC63455 /* HotKey.release.xcconfig */, + 95BE8F0A9508E6F1CD0F8CD41402C6C1 /* SwiftLint-iOS.debug.xcconfig */, + 4DE53E34B2E21972A0204D7A2209E3E3 /* SwiftLint-iOS.release.xcconfig */, + EE5B3CB181C3DDDDC24C0AFF87FAA8A9 /* SwiftLint-macOS.debug.xcconfig */, + 1C8C7BFEF056EC86C14852C4D2A08FF3 /* SwiftLint-macOS.release.xcconfig */, ); name = "Support Files"; - path = "../Target Support Files/HotKey"; + path = "../Target Support Files/SwiftLint-iOS"; sourceTree = ""; }; - E7B6D3933FA52A78D67127559530B1D3 /* Pods */ = { + D5B0301D326B9164D41D55FEAB60C8DA /* iOS */ = { isa = PBXGroup; children = ( - A773AD3680014F234FE336F605154904 /* HotKey */, - 23A5D522D1275959966E7848A1E5D2F8 /* SwiftLint */, + 23370B0E20140BBD63087288D6A26EBA /* Foundation.framework */, ); - name = Pods; + name = iOS; + sourceTree = ""; + }; + E4A2CB193F2CAC354E3C15096484406D /* Frameworks */ = { + isa = PBXGroup; + children = ( + D5B0301D326B9164D41D55FEAB60C8DA /* iOS */, + F3499C270F60422DB1B31AE065001588 /* OS X */, + ); + name = Frameworks; + sourceTree = ""; + }; + F3499C270F60422DB1B31AE065001588 /* OS X */ = { + isa = PBXGroup; + children = ( + 3D486CE56C1DCFDFACC043AA9637506F /* AppKit.framework */, + 4651DB1A3745D6DE10BBAAC032A11263 /* Carbon.framework */, + 5E5A18E310985E019B14966EC691669B /* Cocoa.framework */, + ); + name = "OS X"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - 3337D2EA488A4D285D9488F94E1BAC50 /* Headers */ = { + 3C21E3A60B8D2063E4CC2A0220E45AFD /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + E3EDB632F407232103226EB2AF8C239D /* Charts-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4CE9DA1954C9386316BBABC707CD274D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 794D228A0613A523C567E2154D5AF920 /* Pods-PiBar for iOS-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AD644978FCDF6D2C3396C054A374923E /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - A7CD375B656BAC38CD49FCF05366BA6E /* Pods-PiBar-umbrella.h in Headers */, + 846265AE5C245E369A93B951583BB63C /* Pods-PiBar for macOS-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -246,42 +812,80 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - DB4D977E4336D9F2E9E510A33CD1DB6D /* HotKey */ = { + 25BB1CA875F1C99121F03322AB57347A /* Pods-PiBar for iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = A7CF4E656A9E7278BE7AD5E48841412E /* Build configuration list for PBXNativeTarget "HotKey" */; + buildConfigurationList = 87D7BC6AD0C8F830F928490658C2C599 /* Build configuration list for PBXNativeTarget "Pods-PiBar for iOS" */; buildPhases = ( - F86CE60BF1555823DE98BC5D4354F297 /* Headers */, - 7CBB17719916DF3020AE21CEEA3DD91C /* Sources */, - EC969627A44DD839E017AD83678B6FF2 /* Frameworks */, - B29C2900C4DEA03C97EAAA1E92F0C9FE /* Resources */, + 4CE9DA1954C9386316BBABC707CD274D /* Headers */, + DB05689B63D4DA88E768E3F3A2C62772 /* Sources */, + 0109DF9091F5396F6C628BAE0A6F7CE6 /* Frameworks */, + 3F7C690987F38372CF1092879D762C0C /* Resources */, ); buildRules = ( ); dependencies = ( + 208B83130A5186019A30870D6B8DFC32 /* PBXTargetDependency */, + 1C16C02BDD10625CDE79BFDF4C32C48B /* PBXTargetDependency */, ); - name = HotKey; - productName = HotKey; - productReference = F36B2E7BB4C3E92E4DF7443FAFF3AE3F /* HotKey.framework */; + name = "Pods-PiBar for iOS"; + productName = "Pods-PiBar for iOS"; + productReference = 921C052C34B3D2E3C8EDC80C9C9706BA /* Pods_PiBar_for_iOS.framework */; productType = "com.apple.product-type.framework"; }; - DF53168522C323A8F566844912454C62 /* Pods-PiBar */ = { + 40BD49D951E170B506897E8C3C0C2B06 /* Pods-PiBar for macOS */ = { isa = PBXNativeTarget; - buildConfigurationList = A01C1C8DBC6317058BC3DD33267DEC71 /* Build configuration list for PBXNativeTarget "Pods-PiBar" */; + buildConfigurationList = 769C58ACE31DEFF9EF75CB169618B501 /* Build configuration list for PBXNativeTarget "Pods-PiBar for macOS" */; buildPhases = ( - 3337D2EA488A4D285D9488F94E1BAC50 /* Headers */, - 4FC33F73A6A09E8B0782C3AE6534A57C /* Sources */, - AC925B3BC8F8597F439F47602934FE1D /* Frameworks */, - 61E06547E7192436AE2CF4AE5A7E07EE /* Resources */, + AD644978FCDF6D2C3396C054A374923E /* Headers */, + 6D745A65A804BBFF571F637E57F244C4 /* Sources */, + 9CB1B84B641C42EBDAA1B4B790B06A48 /* Frameworks */, + EB0233C59BCAC3867DFF46C6AAAE4A07 /* Resources */, ); buildRules = ( ); dependencies = ( - FADB4F9C9BC5AF6DC4A42DB414045E59 /* PBXTargetDependency */, - D062C18277883D7A34033314383198E7 /* PBXTargetDependency */, + 300DEC1BDC16212C6FF99DAF89397DA0 /* PBXTargetDependency */, + 64F4324ACB0244A42C4930ABC36AF288 /* PBXTargetDependency */, ); - name = "Pods-PiBar"; - productName = "Pods-PiBar"; - productReference = A362C7E6DCA3AA3091AAC7D820186A93 /* Pods_PiBar.framework */; + name = "Pods-PiBar for macOS"; + productName = "Pods-PiBar for macOS"; + productReference = 7E24678C6B1E49C2024EC300DB731F51 /* Pods_PiBar_for_macOS.framework */; + productType = "com.apple.product-type.framework"; + }; + BF03B0B2B8B052092CB5F90CC5FB3757 /* Charts */ = { + isa = PBXNativeTarget; + buildConfigurationList = EACB1516A3F84CD99E9B870444AB37F1 /* Build configuration list for PBXNativeTarget "Charts" */; + buildPhases = ( + 3C21E3A60B8D2063E4CC2A0220E45AFD /* Headers */, + 6D366EFBB5EBF23E78291B1F4BFCECFA /* Sources */, + 7E4835E1A446981AFA38D55A1D9E0142 /* Frameworks */, + C0AFB00D995D51D37A221C832E39671F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Charts; + productName = Charts; + productReference = A1C1B977ED8804E8AEEC884E7359EE58 /* Charts.framework */; + productType = "com.apple.product-type.framework"; + }; + DB4D977E4336D9F2E9E510A33CD1DB6D /* HotKey */ = { + isa = PBXNativeTarget; + buildConfigurationList = A7CF4E656A9E7278BE7AD5E48841412E /* Build configuration list for PBXNativeTarget "HotKey" */; + buildPhases = ( + F86CE60BF1555823DE98BC5D4354F297 /* Headers */, + 7CBB17719916DF3020AE21CEEA3DD91C /* Sources */, + EC969627A44DD839E017AD83678B6FF2 /* Frameworks */, + B29C2900C4DEA03C97EAAA1E92F0C9FE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = HotKey; + productName = HotKey; + productReference = F36B2E7BB4C3E92E4DF7443FAFF3AE3F /* HotKey.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ @@ -291,10 +895,10 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1100; - LastUpgradeCheck = 1150; + LastUpgradeCheck = 1210; }; buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 10.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -302,19 +906,22 @@ Base, ); mainGroup = CF1408CF629C7361332E53B88F7BD30C; - productRefGroup = 9964B96653A59F2D338403CB3AFF245A /* Products */; + productRefGroup = 6F979098037E24B68A6871AA6574AA60 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( + BF03B0B2B8B052092CB5F90CC5FB3757 /* Charts */, DB4D977E4336D9F2E9E510A33CD1DB6D /* HotKey */, - DF53168522C323A8F566844912454C62 /* Pods-PiBar */, - 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */, + 25BB1CA875F1C99121F03322AB57347A /* Pods-PiBar for iOS */, + 40BD49D951E170B506897E8C3C0C2B06 /* Pods-PiBar for macOS */, + 2B03D5FD26A26B7D62142EA4492AEB83 /* SwiftLint-iOS */, + 9F0E6E0C8BF75091EADD98FA13B1A847 /* SwiftLint-macOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 61E06547E7192436AE2CF4AE5A7E07EE /* Resources */ = { + 3F7C690987F38372CF1092879D762C0C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -328,14 +935,176 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C0AFB00D995D51D37A221C832E39671F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EB0233C59BCAC3867DFF46C6AAAE4A07 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 4FC33F73A6A09E8B0782C3AE6534A57C /* Sources */ = { + 6D366EFBB5EBF23E78291B1F4BFCECFA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0DEDAE490C1A3FA9161CD695C0645004 /* Pods-PiBar-dummy.m in Sources */, + 39E13C8ED0C32E9EC0B353CA45C70CF2 /* AnimatedMoveViewJob.swift in Sources */, + A95748A4D50EEB26B6290061C34DAA6D /* AnimatedViewPortJob.swift in Sources */, + 970B9C4530EA8ACC31B005CE4523C91B /* AnimatedZoomViewJob.swift in Sources */, + 7320CBA29BE1D473CA5BE26B1D226D04 /* Animator.swift in Sources */, + 9400854C32DFF16FD068D280CD43176F /* AxisBase.swift in Sources */, + B14341E138DC6D05193C2866864E9DEC /* AxisRendererBase.swift in Sources */, + 62BEAD080E107195393F8ABBA8C81572 /* BarChartData.swift in Sources */, + 8D62FB3504F2C9B9E0910E493637F7BE /* BarChartDataEntry.swift in Sources */, + 0A30CC2BF450A16F1F4C39C95CFA3A66 /* BarChartDataProvider.swift in Sources */, + 13D3AB43B01F6D78090806390BD29B7E /* BarChartDataSet.swift in Sources */, + 7D605E2EF1E737244832E3329AE89746 /* BarChartRenderer.swift in Sources */, + AD1E6E99CE3807BF3DEE1BEBE6E3CAF2 /* BarChartView.swift in Sources */, + 8606577BE49942FFBE9764F6BAC993A8 /* BarHighlighter.swift in Sources */, + 03D8F8D16FAF2B28113DB82186DBC66F /* BarLineChartViewBase.swift in Sources */, + D9687FA7E8A77056DC5C8444EEA904E5 /* BarLineScatterCandleBubbleChartData.swift in Sources */, + C8092EC19C870273D18B562A7EE9CD0F /* BarLineScatterCandleBubbleChartDataProvider.swift in Sources */, + AEAB95AFD2529CDAF3A10D90A165CF16 /* BarLineScatterCandleBubbleChartDataSet.swift in Sources */, + C7F30DCF3631591639429F38CDA24EBC /* BarLineScatterCandleBubbleRenderer.swift in Sources */, + 7EA6B81624538BCC292467DC0E7832D0 /* BubbleChartData.swift in Sources */, + 02DB3B5C782BC3DEF6B921B9D049201D /* BubbleChartDataEntry.swift in Sources */, + 2E91A4CB83F9DC6BDDAE907A38FE4648 /* BubbleChartDataProvider.swift in Sources */, + 7ACE9E9A28CB14F6DB0D7218C8B50917 /* BubbleChartDataSet.swift in Sources */, + 5D79FD33591AEAEB1B9BF86EC4EA3EA6 /* BubbleChartRenderer.swift in Sources */, + CDABB0AA47F0EEE957FCE629F8D2DB6B /* BubbleChartView.swift in Sources */, + CB6E235EB0A5B896F9D1815877757EF9 /* CandleChartData.swift in Sources */, + 3EAC9B9D96C569653EF71750289EB0D0 /* CandleChartDataEntry.swift in Sources */, + 8CBA559D0C2045A8E6F7F9922C553F0F /* CandleChartDataProvider.swift in Sources */, + 4994E7B2E69CF616E8E7ECD19014CAE0 /* CandleChartDataSet.swift in Sources */, + 1F91BEF50B63CA55CA641D3367FCA3F3 /* CandleStickChartRenderer.swift in Sources */, + 1D2BEF7D292100F700E00314C6FA6FAF /* CandleStickChartView.swift in Sources */, + F1B3693550BD159B7E910C182326A44B /* ChartAnimationEasing.swift in Sources */, + 91FA70B412F137846836139ED890FF6C /* ChartBaseDataSet.swift in Sources */, + 12A718BC5CDEB44AA6B58C8EE7C13128 /* ChartColorTemplates.swift in Sources */, + 7605565F28B5C078923EE0A3F5D7C86E /* ChartData.swift in Sources */, + ABC6878E21B62261BBB4E1631E5EDAF1 /* ChartDataEntry.swift in Sources */, + 83BBD232FE2EA04587B21C81B73AFC09 /* ChartDataEntryBase.swift in Sources */, + 00F52B5B3C90538FE25AEBD9DCC93249 /* ChartDataProvider.swift in Sources */, + C798D8F553DB176CD793E27B4C309DC6 /* ChartDataRendererBase.swift in Sources */, + 8EE80878B32207C5EC1712D83F04983A /* ChartDataSet.swift in Sources */, + 93966518847AB0F104DFCC9DC5B63197 /* ChartHighlighter.swift in Sources */, + 83907D8546A31C4C4B1E31DA90A0CD77 /* ChartLimitLine.swift in Sources */, + 1C2DB744A980164665CE687581222A12 /* Charts-dummy.m in Sources */, + 171D7FC46EC56CF9B16D97877A176C10 /* ChartUtils.swift in Sources */, + 1AF2A57E2B15E7DB326A38D8D8A4FBE2 /* ChartViewBase.swift in Sources */, + 4BBFCE1FBB68AAFB91FF682CD80B9F69 /* ChevronDownShapeRenderer.swift in Sources */, + A86CEF7063CDF44017CBFC67DEA1168E /* ChevronUpShapeRenderer.swift in Sources */, + 2652DBC582FD2BF6AA5B2A40B66020EC /* CircleShapeRenderer.swift in Sources */, + C031EFD3013B5803C3B7A6D07E4721C0 /* CombinedChartData.swift in Sources */, + 34BC5667BA39781B4F70C9AC0518E537 /* CombinedChartDataProvider.swift in Sources */, + 2F819FF7F7721DA042E977EF91BA545D /* CombinedChartRenderer.swift in Sources */, + F2C1EF0010084FA2B769B019EBD6D909 /* CombinedChartView.swift in Sources */, + BDD367ED4BB5510C86658115EE6BE71E /* CombinedHighlighter.swift in Sources */, + 01618174B06DD11428CA21BD4F996704 /* ComponentBase.swift in Sources */, + 74D4DC9B702FBC147C03965E9F271128 /* CrossShapeRenderer.swift in Sources */, + A688509392FC14A799979CEA50908A2D /* DataApproximator+N.swift in Sources */, + DDCAF53B6EE94F6ADF428036C2A85244 /* DataApproximator.swift in Sources */, + 6801E6D885988880F502EFA919360446 /* DefaultAxisValueFormatter.swift in Sources */, + E2FF6F533C0CFB1461BA3D7B76B48A9C /* DefaultFillFormatter.swift in Sources */, + DD3CC52D093C0DF2CF8109C55CFFCD85 /* DefaultValueFormatter.swift in Sources */, + 0388D8402985CBE0DB060BB0D7CCB2ED /* Description.swift in Sources */, + 26EFCEF521B95D2CCB19E5535408A1FA /* Fill.swift in Sources */, + 9F668D7B0F3CEA3608D305A013A7BE48 /* Highlight.swift in Sources */, + 6A532F6858B2D5219D76BEFE1E74A5A6 /* HorizontalBarChartRenderer.swift in Sources */, + 3A5078F021BB004E2F4463F360D41391 /* HorizontalBarChartView.swift in Sources */, + 91704461340ABFBEB88425DCC9867890 /* HorizontalBarHighlighter.swift in Sources */, + 5128310DB4D59B51642B2A13972C3434 /* IAxisValueFormatter.swift in Sources */, + D68F93293888B6776369185028CB60B0 /* IBarChartDataSet.swift in Sources */, + 709152D66394DCE1D7D3802C65090118 /* IBarLineScatterCandleBubbleChartDataSet.swift in Sources */, + 5C790091CB1B0D41E23A4AE5FA727983 /* IBubbleChartDataSet.swift in Sources */, + 27303BB4BDD2C456C1DEC36087E756B4 /* ICandleChartDataSet.swift in Sources */, + 62C2F7F87CA413E2407F1F8DECA88949 /* IChartDataSet.swift in Sources */, + 35C57ED01BE2D7FD6F4DACD96F5EDFE1 /* IFillFormatter.swift in Sources */, + CEC814CFD549BCDDAB65F5918969182E /* IHighlighter.swift in Sources */, + 58398B0E03224E5EBE7FCFAD62F8FB69 /* ILineChartDataSet.swift in Sources */, + 3D57709B53538839313AA6A6DC6E322C /* ILineRadarChartDataSet.swift in Sources */, + B45CE2B91939EC0EA7898C1C6BA16367 /* ILineScatterCandleRadarChartDataSet.swift in Sources */, + 75B22CB8C580F73B117214B061436099 /* IMarker.swift in Sources */, + C2012E1E9FD44B75ECF4B4B3D3782293 /* IndexAxisValueFormatter.swift in Sources */, + 675D24D585A2506762D77E6052517E00 /* IPieChartDataSet.swift in Sources */, + FC378CAAA9FD5B4B156B982EBDB12BA3 /* IRadarChartDataSet.swift in Sources */, + AA39F0560285BF5448411D429ED9408D /* IScatterChartDataSet.swift in Sources */, + ECC149B136D4D63AF251912E051A142F /* IShapeRenderer.swift in Sources */, + 5B96DE0FB615C832ACB06B9E16B49740 /* IValueFormatter.swift in Sources */, + 9C081B18BB1A54D953EB89605319AA77 /* Legend.swift in Sources */, + 1EA6964A5C78B205DFC45AFC08637DF8 /* LegendEntry.swift in Sources */, + 4DB24C948D1C4F4FB43DAA833D049885 /* LegendRenderer.swift in Sources */, + ED58A23EB5289726B5D74EFEEA590BDD /* LineChartData.swift in Sources */, + C5880AE977F94A1AE25E756A805B7B0B /* LineChartDataProvider.swift in Sources */, + 66FC952C43367208D641932AEB59DA8A /* LineChartDataSet.swift in Sources */, + FEFD4F20D5515E1D596FCFA083EB05D7 /* LineChartRenderer.swift in Sources */, + E73D9F2950182C774E50E891F75C1BB4 /* LineChartView.swift in Sources */, + 53772EF4F22DD42A63551C4CD687E9A6 /* LineRadarChartDataSet.swift in Sources */, + 8E01F38A1667D8B2C57E20400D6BB425 /* LineRadarRenderer.swift in Sources */, + 5E27C6B54F72D3C34AB6605772DF7E70 /* LineScatterCandleRadarChartDataSet.swift in Sources */, + DC28EC495C8806D9B9C0CB0C2F8E8673 /* LineScatterCandleRadarRenderer.swift in Sources */, + 81FCFDD76834B83BF46D3741C79FF29A /* MarkerImage.swift in Sources */, + 32B122E4A8FECD28F192174EA3A4C704 /* MarkerView.swift in Sources */, + D7B623CFD7231E00FB60419AA17A3E82 /* MoveViewJob.swift in Sources */, + 4D9563B240B9127EDF6FBA5A21180FD8 /* PieChartData.swift in Sources */, + AF34EC6CDB336F35ACD18C2CA8428251 /* PieChartDataEntry.swift in Sources */, + 2AFDC0B52F5347C726EAAD89365E3978 /* PieChartDataSet.swift in Sources */, + BB75859BDCF578D67C08E6E6741701EB /* PieChartRenderer.swift in Sources */, + 1132976D856E1857C4CD9989B9A8EBCE /* PieChartView.swift in Sources */, + C742D0AA9431960E21B6203AF6D2F62B /* PieHighlighter.swift in Sources */, + 8104AC0D8B60EEC6FB5C73EAFA1934AA /* PieRadarChartViewBase.swift in Sources */, + 644CBE15115BFEDB16BF372F81CFF076 /* PieRadarHighlighter.swift in Sources */, + 5847AEA5F6021F082A9238BDD5801079 /* Platform+Accessibility.swift in Sources */, + D885D137C7BBBBA479BDB92A72B7D04E /* Platform+Color.swift in Sources */, + 640C152125D86F7A3C9BDD7D18563CBA /* Platform+Gestures.swift in Sources */, + 474E946BEBC8E12EA9C8D766BDFCEEF5 /* Platform+Graphics.swift in Sources */, + C45A1A018758BC37A59C89114F9A4F75 /* Platform+Touch Handling.swift in Sources */, + 1F33F0B7F8701E9A20F90C6A32B2CA03 /* Platform.swift in Sources */, + 4975CACC893A427910F64270C28E7275 /* RadarChartData.swift in Sources */, + C72D7A57303FE4E7E25AB747DD3A85AC /* RadarChartDataEntry.swift in Sources */, + 930F4FD273BB41A8D1BB1F07608B3028 /* RadarChartDataSet.swift in Sources */, + 683F5BAD171DD257F3EC9472323B4BC7 /* RadarChartRenderer.swift in Sources */, + 25D675B271C3E073CF2C27B945FAADBB /* RadarChartView.swift in Sources */, + 61B753A042411BCF77F768D958106A5C /* RadarHighlighter.swift in Sources */, + 7731319719C1878AE7AF9D78705712AF /* Range.swift in Sources */, + C6F4045EAF5410768B595ED98863A250 /* Renderer.swift in Sources */, + 66B829842549EECCE91E61D34FFABCBA /* ScatterChartData.swift in Sources */, + C5A3430178D9993D8A5A13864AE40A45 /* ScatterChartDataProvider.swift in Sources */, + CAE986D939019B7726C896B88AC34B82 /* ScatterChartDataSet.swift in Sources */, + E999737BF0D21446B7FFE4DA783EACB2 /* ScatterChartRenderer.swift in Sources */, + 991A4EA362C241D77DF184594E226E93 /* ScatterChartView.swift in Sources */, + 5DA6F4FC8413B90BD793D0AE181C2AA9 /* SquareShapeRenderer.swift in Sources */, + 64A0821B4F9954E56639DAF9BD7E575C /* Transformer.swift in Sources */, + 94921CD3D3FBA3D6572678B1F59AA957 /* TransformerHorizontalBarChart.swift in Sources */, + 09D2EA12CED518BAA4BCB6A707FA3125 /* TriangleShapeRenderer.swift in Sources */, + E4131026AAE1E3333FB46E14CED55F2A /* ViewPortHandler.swift in Sources */, + 0E7DC6451BEFB26E3A47621C0B7F1C16 /* ViewPortJob.swift in Sources */, + B00A1568A89DD820BC9063DC6961540D /* XAxis.swift in Sources */, + A710DCACFF8FFEA66D7ACC5D1BD95C1D /* XAxisRenderer.swift in Sources */, + 0769C281810E13C0985B2A414F20ADDE /* XAxisRendererHorizontalBarChart.swift in Sources */, + 31918F06050D6445E06FEDF8E2183AF6 /* XAxisRendererRadarChart.swift in Sources */, + D6E011A7BA193945CEA69517E40CBD48 /* XShapeRenderer.swift in Sources */, + BCD42880A54D247B7A68AC06335D0AF5 /* YAxis.swift in Sources */, + B65DAD4280582DBED3A5A2C62185B389 /* YAxisRenderer.swift in Sources */, + B87DABFAA95CDA5DFDDD2F409FDD5DF3 /* YAxisRendererHorizontalBarChart.swift in Sources */, + 6FDD5EA5214B8CDC0E747916F07D74C2 /* YAxisRendererRadarChart.swift in Sources */, + 22681421488F1E799B4C8FFC0A77F29B /* ZoomViewJob.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6D745A65A804BBFF571F637E57F244C4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7AF1F2C1A31F465C48612CF03B985908 /* Pods-PiBar for macOS-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -352,31 +1121,161 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DB05689B63D4DA88E768E3F3A2C62772 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EFB62053CDB41361478C397163454F5E /* Pods-PiBar for iOS-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - D062C18277883D7A34033314383198E7 /* PBXTargetDependency */ = { + 1C16C02BDD10625CDE79BFDF4C32C48B /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = SwiftLint; - target = 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */; - targetProxy = 0F27C02292C675A2D7AF4DF1D1D00110 /* PBXContainerItemProxy */; + name = "SwiftLint-iOS"; + target = 2B03D5FD26A26B7D62142EA4492AEB83 /* SwiftLint-iOS */; + targetProxy = 2B6E3223817FB0BB59F76C35B360A31C /* PBXContainerItemProxy */; }; - FADB4F9C9BC5AF6DC4A42DB414045E59 /* PBXTargetDependency */ = { + 208B83130A5186019A30870D6B8DFC32 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Charts; + target = BF03B0B2B8B052092CB5F90CC5FB3757 /* Charts */; + targetProxy = 4EB585088F1252F8E9E2E2C41BEEBF99 /* PBXContainerItemProxy */; + }; + 300DEC1BDC16212C6FF99DAF89397DA0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = HotKey; target = DB4D977E4336D9F2E9E510A33CD1DB6D /* HotKey */; - targetProxy = 8D7EB7FF347D5F06486B82FC5EF3E49C /* PBXContainerItemProxy */; + targetProxy = C5B8520C09E77C7BA38315A81B959B26 /* PBXContainerItemProxy */; + }; + 64F4324ACB0244A42C4930ABC36AF288 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "SwiftLint-macOS"; + target = 9F0E6E0C8BF75091EADD98FA13B1A847 /* SwiftLint-macOS */; + targetProxy = 0391AC22223E7714EBFE9A97D49B1118 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 02B37FD027A1BAF89CC8F8FC486471A4 /* Release */ = { + 01D62C2585E665AE33F274C55E455E95 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E2CD43DAA14458EE1E23C193BD29E52D /* SwiftLint.release.xcconfig */; + baseConfigurationReference = C7474834432C57BA0DCB62390276C8F3 /* Pods-PiBar for iOS.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 17606AAB034FEE03D25DFAB1CFFD49F1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0DCE4277E1341B290A8E556C5C2B19FC /* Charts.release.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Charts/Charts-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Charts/Charts-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Charts/Charts.modulemap"; + PRODUCT_MODULE_NAME = Charts; + PRODUCT_NAME = Charts; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.3; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 2511E7F3E57322AEBE4B11519B10390C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8874EA9D56F2D25ADC0F27B429AFE971 /* Pods-PiBar for iOS.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 308445465FA89F53CB5C7ABCE4D0879D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EE5B3CB181C3DDDDC24C0AFF87FAA8A9 /* SwiftLint-macOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -385,48 +1284,97 @@ MACOSX_DEPLOYMENT_TARGET = 10.6; SDKROOT = macosx; }; - name = Release; + name = Debug; }; - 04EF0E58D5857015B374735D5CCE3571 /* Release */ = { + 4C32297FE058B4F351F4018647F9B9FC /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3189FE65632B209B3608F60B0CC63455 /* HotKey.release.xcconfig */; + baseConfigurationReference = 1C699EB5CCB69D147DDF43F493B1D1D6 /* Charts.debug.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - GCC_PREFIX_HEADER = "Target Support Files/HotKey/HotKey-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/HotKey/HotKey-Info.plist"; + GCC_PREFIX_HEADER = "Target Support Files/Charts/Charts-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Charts/Charts-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "@executable_path/../Frameworks", + "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.9; - MODULEMAP_FILE = "Target Support Files/HotKey/HotKey.modulemap"; - PRODUCT_MODULE_NAME = HotKey; - PRODUCT_NAME = HotKey; - SDKROOT = macosx; + MODULEMAP_FILE = "Target Support Files/Charts/Charts.modulemap"; + PRODUCT_MODULE_NAME = Charts; + PRODUCT_NAME = Charts; + SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 5.3; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; + name = Debug; + }; + 6824828866768D8ED7B2856CF8729ADC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 95BE8F0A9508E6F1CD0F8CD41402C6C1 /* SwiftLint-iOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6DA24DA71EA8E8EA3E39B09D5D91D72C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4DE53E34B2E21972A0204D7A2209E3E3 /* SwiftLint-iOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 76F6A7ABE75438EF40E43E81A0A84DB0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1C8C7BFEF056EC86C14852C4D2A08FF3 /* SwiftLint-macOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.6; + SDKROOT = macosx; + }; name = Release; }; - 2AE0D8B39827576460938522CAB2F560 /* Release */ = { + 874D10C74FC21F831E0E1A17109CA076 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -449,6 +1397,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -456,13 +1405,16 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; + DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_RELEASE=1", + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -471,25 +1423,26 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = NO; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; - name = Release; + name = Debug; }; - 6F01B14518E78EDCE50F83DD8D67019B /* Release */ = { + D12E34BC9EC4B99CD4C0294462C83784 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CDE189E76FE5BFB347A2E8F628AF8188 /* Pods-PiBar.release.xcconfig */; + baseConfigurationReference = DB3547D8AACEFE6671353ACE26F88A3A /* Pods-PiBar for macOS.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; @@ -499,8 +1452,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = "Target Support Files/Pods-PiBar/Pods-PiBar-Info.plist"; + INFOPLIST_FILE = "Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -509,7 +1461,7 @@ ); MACH_O_TYPE = staticlib; MACOSX_DEPLOYMENT_TARGET = 10.12; - MODULEMAP_FILE = "Target Support Files/Pods-PiBar/Pods-PiBar.modulemap"; + MODULEMAP_FILE = "Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; @@ -520,12 +1472,13 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Release; + name = Debug; }; - 816C679D58750316935F7386A438309D /* Debug */ = { + D537E3BF54082391F52F5EC091E9D8D0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -548,6 +1501,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -555,16 +1509,13 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_DEBUG=1", - "DEBUG=1", + "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -573,26 +1524,25 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; - name = Debug; + name = Release; }; - B290702DE9ACC00B7D5B722D65FA85F2 /* Debug */ = { + F20F055EA1EE7A2D084A856FEC917471 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FAC1F45CCB8C15294E87E77FEA979581 /* Pods-PiBar.debug.xcconfig */; + baseConfigurationReference = F058BBA8A2B5559901468CC2B693EA26 /* Pods-PiBar for macOS.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; @@ -602,8 +1552,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = "Target Support Files/Pods-PiBar/Pods-PiBar-Info.plist"; + INFOPLIST_FILE = "Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -612,7 +1561,7 @@ ); MACH_O_TYPE = staticlib; MACOSX_DEPLOYMENT_TARGET = 10.12; - MODULEMAP_FILE = "Target Support Files/Pods-PiBar/Pods-PiBar.modulemap"; + MODULEMAP_FILE = "Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; @@ -623,30 +1572,46 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Debug; + name = Release; }; - C58C9D16B93F5A16ABCC3D064725954E /* Debug */ = { + F4125A2AAC0183FC0C901DA8618FC0A3 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C45D6A9423CD5A65CB89B7FAB8572897 /* SwiftLint.debug.xcconfig */; + baseConfigurationReference = 09CFF440A13A62AE14A558484BC4B5D0 /* HotKey.release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/HotKey/HotKey-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/HotKey/HotKey-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", + "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.6; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MODULEMAP_FILE = "Target Support Files/HotKey/HotKey.modulemap"; + PRODUCT_MODULE_NAME = HotKey; + PRODUCT_NAME = HotKey; SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; - name = Debug; + name = Release; }; - F8D77CB5291A6FB3883B7E6A38F90D79 /* Debug */ = { + FE4DEA23EB21E13725D99942F2CA4422 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 39D6FC83A74766CE2022EE87101A6CAE /* HotKey.debug.xcconfig */; + baseConfigurationReference = CDF734830016E3088136D5243F547F5D /* HotKey.debug.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; @@ -656,7 +1621,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; GCC_PREFIX_HEADER = "Target Support Files/HotKey/HotKey-prefix.pch"; INFOPLIST_FILE = "Target Support Files/HotKey/HotKey-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -681,20 +1645,47 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 0D0981C130B08F416BEB2EAEC0323E63 /* Build configuration list for PBXAggregateTarget "SwiftLint-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 308445465FA89F53CB5C7ABCE4D0879D /* Debug */, + 76F6A7ABE75438EF40E43E81A0A84DB0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 15C2581D1D019FF8D7F783E76C884B84 /* Build configuration list for PBXAggregateTarget "SwiftLint-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6824828866768D8ED7B2856CF8729ADC /* Debug */, + 6DA24DA71EA8E8EA3E39B09D5D91D72C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( - 816C679D58750316935F7386A438309D /* Debug */, - 2AE0D8B39827576460938522CAB2F560 /* Release */, + 874D10C74FC21F831E0E1A17109CA076 /* Debug */, + D537E3BF54082391F52F5EC091E9D8D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 769C58ACE31DEFF9EF75CB169618B501 /* Build configuration list for PBXNativeTarget "Pods-PiBar for macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D12E34BC9EC4B99CD4C0294462C83784 /* Debug */, + F20F055EA1EE7A2D084A856FEC917471 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - A01C1C8DBC6317058BC3DD33267DEC71 /* Build configuration list for PBXNativeTarget "Pods-PiBar" */ = { + 87D7BC6AD0C8F830F928490658C2C599 /* Build configuration list for PBXNativeTarget "Pods-PiBar for iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - B290702DE9ACC00B7D5B722D65FA85F2 /* Debug */, - 6F01B14518E78EDCE50F83DD8D67019B /* Release */, + 01D62C2585E665AE33F274C55E455E95 /* Debug */, + 2511E7F3E57322AEBE4B11519B10390C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -702,17 +1693,17 @@ A7CF4E656A9E7278BE7AD5E48841412E /* Build configuration list for PBXNativeTarget "HotKey" */ = { isa = XCConfigurationList; buildConfigurations = ( - F8D77CB5291A6FB3883B7E6A38F90D79 /* Debug */, - 04EF0E58D5857015B374735D5CCE3571 /* Release */, + FE4DEA23EB21E13725D99942F2CA4422 /* Debug */, + F4125A2AAC0183FC0C901DA8618FC0A3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - AE7B4FB01588B9E6DF09CB79FC7CE7BD /* Build configuration list for PBXAggregateTarget "SwiftLint" */ = { + EACB1516A3F84CD99E9B870444AB37F1 /* Build configuration list for PBXNativeTarget "Charts" */ = { isa = XCConfigurationList; buildConfigurations = ( - C58C9D16B93F5A16ABCC3D064725954E /* Debug */, - 02B37FD027A1BAF89CC8F8FC486471A4 /* Release */, + 4C32297FE058B4F351F4018647F9B9FC /* Debug */, + 17606AAB034FEE03D25DFAB1CFFD49F1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Pods/SwiftLint/swiftlint b/Pods/SwiftLint/swiftlint index 45438c4..e5240bf 100755 Binary files a/Pods/SwiftLint/swiftlint and b/Pods/SwiftLint/swiftlint differ diff --git a/Pods/Target Support Files/Charts/Charts-Info.plist b/Pods/Target Support Files/Charts/Charts-Info.plist new file mode 100644 index 0000000..fd3425e --- /dev/null +++ b/Pods/Target Support Files/Charts/Charts-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.6.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Charts/Charts-dummy.m b/Pods/Target Support Files/Charts/Charts-dummy.m new file mode 100644 index 0000000..0d0640c --- /dev/null +++ b/Pods/Target Support Files/Charts/Charts-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Charts : NSObject +@end +@implementation PodsDummy_Charts +@end diff --git a/Pods/Target Support Files/Charts/Charts-prefix.pch b/Pods/Target Support Files/Charts/Charts-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Pods/Target Support Files/Charts/Charts-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/Charts/Charts-umbrella.h b/Pods/Target Support Files/Charts/Charts-umbrella.h new file mode 100644 index 0000000..006a8a2 --- /dev/null +++ b/Pods/Target Support Files/Charts/Charts-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double ChartsVersionNumber; +FOUNDATION_EXPORT const unsigned char ChartsVersionString[]; + diff --git a/Pods/Target Support Files/Charts/Charts.debug.xcconfig b/Pods/Target Support Files/Charts/Charts.debug.xcconfig new file mode 100644 index 0000000..74c1b06 --- /dev/null +++ b/Pods/Target Support Files/Charts/Charts.debug.xcconfig @@ -0,0 +1,12 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Charts +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Charts +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Charts/Charts.modulemap b/Pods/Target Support Files/Charts/Charts.modulemap new file mode 100644 index 0000000..663dec7 --- /dev/null +++ b/Pods/Target Support Files/Charts/Charts.modulemap @@ -0,0 +1,6 @@ +framework module Charts { + umbrella header "Charts-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Charts/Charts.release.xcconfig b/Pods/Target Support Files/Charts/Charts.release.xcconfig new file mode 100644 index 0000000..74c1b06 --- /dev/null +++ b/Pods/Target Support Files/Charts/Charts.release.xcconfig @@ -0,0 +1,12 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Charts +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Charts +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/HotKey/HotKey.debug.xcconfig b/Pods/Target Support Files/HotKey/HotKey.debug.xcconfig index b46d611..133b12a 100644 --- a/Pods/Target Support Files/HotKey/HotKey.debug.xcconfig +++ b/Pods/Target Support Files/HotKey/HotKey.debug.xcconfig @@ -1,3 +1,4 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CODE_SIGN_IDENTITY = CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/HotKey GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 @@ -7,6 +8,7 @@ PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/HotKey +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/HotKey/HotKey.release.xcconfig b/Pods/Target Support Files/HotKey/HotKey.release.xcconfig index b46d611..133b12a 100644 --- a/Pods/Target Support Files/HotKey/HotKey.release.xcconfig +++ b/Pods/Target Support Files/HotKey/HotKey.release.xcconfig @@ -1,3 +1,4 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CODE_SIGN_IDENTITY = CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/HotKey GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 @@ -7,6 +8,7 @@ PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/HotKey +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-Info.plist b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-Info.plist similarity index 100% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar-Info.plist rename to Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-Info.plist diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-acknowledgements.markdown b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-acknowledgements.markdown new file mode 100644 index 0000000..6246781 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-acknowledgements.markdown @@ -0,0 +1,234 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## SwiftLint + +The MIT License (MIT) + +Copyright (c) 2020 Realm Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +## Charts + +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-2019 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. + + +Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-acknowledgements.plist b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-acknowledgements.plist new file mode 100644 index 0000000..75b0dfa --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-acknowledgements.plist @@ -0,0 +1,272 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2020 Realm Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + License + MIT + Title + SwiftLint + Type + PSGroupSpecifier + + + FooterText + 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-2019 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. + + + License + Apache License, Version 2.0 + Title + Charts + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-dummy.m b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-dummy.m new file mode 100644 index 0000000..61df849 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_PiBar_for_iOS : NSObject +@end +@implementation PodsDummy_Pods_PiBar_for_iOS +@end diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Debug-input-files.xcfilelist new file mode 100644 index 0000000..5678ea7 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Debug-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks.sh +${BUILT_PRODUCTS_DIR}/Charts/Charts.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Debug-output-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Debug-output-files.xcfilelist new file mode 100644 index 0000000..ad6d655 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Debug-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Release-input-files.xcfilelist new file mode 100644 index 0000000..5678ea7 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Release-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks.sh +${BUILT_PRODUCTS_DIR}/Charts/Charts.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Release-output-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Release-output-files.xcfilelist new file mode 100644 index 0000000..ad6d655 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks-Release-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks.sh b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks.sh new file mode 100755 index 0000000..c1cc936 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-frameworks.sh @@ -0,0 +1,185 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then + # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy + # frameworks to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" +BCSYMBOLMAP_DIR="BCSymbolMaps" + + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +# Copies and strips a vendored framework +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then + # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied + find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do + echo "Installing $f" + install_bcsymbolmap "$f" "$destination" + rm "$f" + done + rmdir "${source}/${BCSYMBOLMAP_DIR}" + fi + + # Use filter instead of exclude so missing patterns don't throw errors. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + elif [ -L "${binary}" ]; then + echo "Destination binary is symlinked..." + dirname="$(dirname "${binary}")" + binary="${dirname}/$(readlink "${binary}")" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} +# Copies and strips a vendored dSYM +install_dsym() { + local source="$1" + warn_missing_arch=${2:-true} + if [ -r "$source" ]; then + # Copy the dSYM into the targets temp dir. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" + + local basename + basename="$(basename -s .dSYM "$source")" + binary_name="$(ls "$source/Contents/Resources/DWARF")" + binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" + + # Strip invalid architectures from the dSYM. + if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then + strip_invalid_archs "$binary" "$warn_missing_arch" + fi + if [[ $STRIP_BINARY_RETVAL == 0 ]]; then + # Move the stripped file into its final destination. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" + else + # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. + touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" + fi + fi +} + +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + warn_missing_arch=${2:-true} + # Get architectures for current target binary + binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" + # Intersect them with the architectures we are building for + intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" + # If there are no archs supported by this binary then warn the user + if [[ -z "$intersected_archs" ]]; then + if [[ "$warn_missing_arch" == "true" ]]; then + echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." + fi + STRIP_BINARY_RETVAL=1 + return + fi + stripped="" + for arch in $binary_archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi + STRIP_BINARY_RETVAL=0 +} + +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" + + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework" +fi +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-umbrella.h b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-umbrella.h new file mode 100644 index 0000000..4a339da --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_PiBar_for_iOSVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_PiBar_for_iOSVersionString[]; + diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.debug.xcconfig b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.debug.xcconfig new file mode 100644 index 0000000..0d571b2 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.debug.xcconfig @@ -0,0 +1,14 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Charts" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Charts/Charts.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "Charts" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.modulemap b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.modulemap new file mode 100644 index 0000000..105a248 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.modulemap @@ -0,0 +1,6 @@ +framework module Pods_PiBar_for_iOS { + umbrella header "Pods-PiBar for iOS-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.release.xcconfig b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.release.xcconfig new file mode 100644 index 0000000..0d571b2 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for iOS/Pods-PiBar for iOS.release.xcconfig @@ -0,0 +1,14 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Charts" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Charts/Charts.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "Charts" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-Info.plist b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-Info.plist new file mode 100644 index 0000000..2243fe6 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-acknowledgements.markdown b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-acknowledgements.markdown similarity index 100% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar-acknowledgements.markdown rename to Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-acknowledgements.markdown diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-acknowledgements.plist b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-acknowledgements.plist similarity index 100% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar-acknowledgements.plist rename to Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-acknowledgements.plist diff --git a/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-dummy.m b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-dummy.m new file mode 100644 index 0000000..b2f8509 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_PiBar_for_macOS : NSObject +@end +@implementation PodsDummy_Pods_PiBar_for_macOS +@end diff --git a/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Debug-input-files.xcfilelist new file mode 100644 index 0000000..d14e2a0 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Debug-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks.sh +${BUILT_PRODUCTS_DIR}/HotKey/HotKey.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Debug-output-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Debug-output-files.xcfilelist similarity index 100% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Debug-output-files.xcfilelist rename to Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Debug-output-files.xcfilelist diff --git a/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Release-input-files.xcfilelist new file mode 100644 index 0000000..d14e2a0 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Release-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks.sh +${BUILT_PRODUCTS_DIR}/HotKey/HotKey.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Release-output-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Release-output-files.xcfilelist similarity index 100% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Release-output-files.xcfilelist rename to Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks-Release-output-files.xcfilelist diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks.sh b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks.sh similarity index 89% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks.sh rename to Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks.sh index 0acd117..5374819 100755 --- a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks.sh +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-frameworks.sh @@ -19,9 +19,8 @@ mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" +BCSYMBOLMAP_DIR="BCSymbolMaps" -# Used as a return value for each invocation of `strip_invalid_archs` function. -STRIP_BINARY_RETVAL=0 # This protects against multiple targets copying the same framework dependency at the same time. The solution # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html @@ -45,6 +44,16 @@ install_framework() source="$(readlink "${source}")" fi + if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then + # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied + find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do + echo "Installing $f" + install_bcsymbolmap "$f" "$destination" + rm "$f" + done + rmdir "${source}/${BCSYMBOLMAP_DIR}" + fi + # Use filter instead of exclude so missing patterns don't throw errors. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" @@ -80,7 +89,6 @@ install_framework() done fi } - # Copies and strips a vendored dSYM install_dsym() { local source="$1" @@ -95,12 +103,11 @@ install_dsym() { binary_name="$(ls "$source/Contents/Resources/DWARF")" binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" - # Strip invalid architectures so "fat" simulator / device frameworks work on device + # Strip invalid architectures from the dSYM. if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then strip_invalid_archs "$binary" "$warn_missing_arch" fi - - if [[ $STRIP_BINARY_RETVAL == 1 ]]; then + if [[ $STRIP_BINARY_RETVAL == 0 ]]; then # Move the stripped file into its final destination. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" @@ -111,28 +118,8 @@ install_dsym() { fi } -# Copies the bcsymbolmap files of a vendored framework -install_bcsymbolmap() { - local bcsymbolmap_path="$1" - local destination="${BUILT_PRODUCTS_DIR}" - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" -} - -# Signs a framework with the provided identity -code_sign_if_enabled() { - if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then - # Use the current code_sign_identity - echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" - local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" - - if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - code_sign_cmd="$code_sign_cmd &" - fi - echo "$code_sign_cmd" - eval "$code_sign_cmd" - fi -} +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 # Strip invalid architectures strip_invalid_archs() { @@ -147,7 +134,7 @@ strip_invalid_archs() { if [[ "$warn_missing_arch" == "true" ]]; then echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." fi - STRIP_BINARY_RETVAL=0 + STRIP_BINARY_RETVAL=1 return fi stripped="" @@ -161,40 +148,31 @@ strip_invalid_archs() { if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi - STRIP_BINARY_RETVAL=1 + STRIP_BINARY_RETVAL=0 } -install_artifact() { - artifact="$1" - base="$(basename "$artifact")" - case $base in - *.framework) - install_framework "$artifact" - ;; - *.dSYM) - # Suppress arch warnings since XCFrameworks will include many dSYM files - install_dsym "$artifact" "false" - ;; - *.bcsymbolmap) - install_bcsymbolmap "$artifact" - ;; - *) - echo "error: Unrecognized artifact "$artifact"" - ;; - esac +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" } -copy_artifacts() { - file_list="$1" - while read artifact; do - install_artifact "$artifact" - done <$file_list -} +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" -ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt" -if [ -r "${ARTIFACT_LIST_FILE}" ]; then - copy_artifacts "${ARTIFACT_LIST_FILE}" -fi + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} if [[ "$CONFIGURATION" == "Debug" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/HotKey/HotKey.framework" diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-umbrella.h b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-umbrella.h similarity index 59% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar-umbrella.h rename to Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-umbrella.h index ccea7d1..e3fe2d3 100644 --- a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-umbrella.h +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS-umbrella.h @@ -11,6 +11,6 @@ #endif -FOUNDATION_EXPORT double Pods_PiBarVersionNumber; -FOUNDATION_EXPORT const unsigned char Pods_PiBarVersionString[]; +FOUNDATION_EXPORT double Pods_PiBar_for_macOSVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_PiBar_for_macOSVersionString[]; diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar.debug.xcconfig b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.debug.xcconfig similarity index 84% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar.debug.xcconfig rename to Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.debug.xcconfig index cb3541c..d166703 100644 --- a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar.debug.xcconfig +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.debug.xcconfig @@ -1,4 +1,5 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HotKey" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HotKey/HotKey.framework/Headers" @@ -9,4 +10,5 @@ PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_ROOT = ${SRCROOT}/Pods +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.modulemap b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.modulemap new file mode 100644 index 0000000..88313f0 --- /dev/null +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.modulemap @@ -0,0 +1,6 @@ +framework module Pods_PiBar_for_macOS { + umbrella header "Pods-PiBar for macOS-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar.release.xcconfig b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.release.xcconfig similarity index 84% rename from Pods/Target Support Files/Pods-PiBar/Pods-PiBar.release.xcconfig rename to Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.release.xcconfig index cb3541c..d166703 100644 --- a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar.release.xcconfig +++ b/Pods/Target Support Files/Pods-PiBar for macOS/Pods-PiBar for macOS.release.xcconfig @@ -1,4 +1,5 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HotKey" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HotKey/HotKey.framework/Headers" @@ -9,4 +10,5 @@ PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_ROOT = ${SRCROOT}/Pods +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-dummy.m b/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-dummy.m deleted file mode 100644 index c449519..0000000 --- a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_Pods_PiBar : NSObject -@end -@implementation PodsDummy_Pods_PiBar -@end diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Debug-input-files.xcfilelist deleted file mode 100644 index 99bf26d..0000000 --- a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Debug-input-files.xcfilelist +++ /dev/null @@ -1,2 +0,0 @@ -${PODS_ROOT}/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks.sh -${BUILT_PRODUCTS_DIR}/HotKey/HotKey.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Release-input-files.xcfilelist deleted file mode 100644 index 99bf26d..0000000 --- a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks-Release-input-files.xcfilelist +++ /dev/null @@ -1,2 +0,0 @@ -${PODS_ROOT}/Target Support Files/Pods-PiBar/Pods-PiBar-frameworks.sh -${BUILT_PRODUCTS_DIR}/HotKey/HotKey.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar.modulemap b/Pods/Target Support Files/Pods-PiBar/Pods-PiBar.modulemap deleted file mode 100644 index 304c81f..0000000 --- a/Pods/Target Support Files/Pods-PiBar/Pods-PiBar.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Pods_PiBar { - umbrella header "Pods-PiBar-umbrella.h" - - export * - module * { export * } -} diff --git a/Pods/Target Support Files/SwiftLint-iOS/SwiftLint-iOS.debug.xcconfig b/Pods/Target Support Files/SwiftLint-iOS/SwiftLint-iOS.debug.xcconfig new file mode 100644 index 0000000..8e67e87 --- /dev/null +++ b/Pods/Target Support Files/SwiftLint-iOS/SwiftLint-iOS.debug.xcconfig @@ -0,0 +1,11 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint-iOS +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/SwiftLint-iOS/SwiftLint-iOS.release.xcconfig b/Pods/Target Support Files/SwiftLint-iOS/SwiftLint-iOS.release.xcconfig new file mode 100644 index 0000000..8e67e87 --- /dev/null +++ b/Pods/Target Support Files/SwiftLint-iOS/SwiftLint-iOS.release.xcconfig @@ -0,0 +1,11 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint-iOS +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/SwiftLint/SwiftLint.debug.xcconfig b/Pods/Target Support Files/SwiftLint-macOS/SwiftLint-macOS.debug.xcconfig similarity index 76% rename from Pods/Target Support Files/SwiftLint/SwiftLint.debug.xcconfig rename to Pods/Target Support Files/SwiftLint-macOS/SwiftLint-macOS.debug.xcconfig index 1f9084d..5aa7d74 100644 --- a/Pods/Target Support Files/SwiftLint/SwiftLint.debug.xcconfig +++ b/Pods/Target Support Files/SwiftLint-macOS/SwiftLint-macOS.debug.xcconfig @@ -1,10 +1,12 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CODE_SIGN_IDENTITY = -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint-macOS GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/SwiftLint/SwiftLint.release.xcconfig b/Pods/Target Support Files/SwiftLint-macOS/SwiftLint-macOS.release.xcconfig similarity index 76% rename from Pods/Target Support Files/SwiftLint/SwiftLint.release.xcconfig rename to Pods/Target Support Files/SwiftLint-macOS/SwiftLint-macOS.release.xcconfig index 1f9084d..5aa7d74 100644 --- a/Pods/Target Support Files/SwiftLint/SwiftLint.release.xcconfig +++ b/Pods/Target Support Files/SwiftLint-macOS/SwiftLint-macOS.release.xcconfig @@ -1,10 +1,12 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CODE_SIGN_IDENTITY = -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint-macOS GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/README.md b/README.md index 831b844..5dd7edd 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,51 @@ -# PiBar for macOS +# PiBar for Pi-hole -PiBar gives you all the tools you need to manage your [Pi-hole](https://pi-hole.net)(s) right from your macOS menu bar. +PiBar gives you all the tools you need to manage your [Pi-hole](https://pi-hole.net)(s) within macOS, iOS, or iPadOS. -## Features -- Display DNS query stats in your macOS menu bar +## Features +- Convienent Pi-hole controls tailored to each platform - Supports multiple Pi-holes (inc. quadruple failover setups!) -- Toggle your Pi-hole(s) on/off from the menu or anywhere via ⌘⌥⇧P - Displays warnings if any or all of your Pi-holes are inaccessible or disabled - No capital "h" in the word "hole" in the app or in the code - Beautiful app icon - Totally FOSS + +## iOS / iPadOS Features +- "Family Mode" simplifies app controls for family members +- Query log with ability to add to allow / deny lists +- Customizable user interface color with full light / dark mode support +- Widgets *(Coming Soon)* +- Apple Watch app and complications *(Coming Soon)* +- Supports iOS & iPadOS 12.4 and later + +## macOS Features + +- Display DNS query stats in your macOS menu bar +- Toggle your Pi-hole(s) on/off from the menu or anywhere via ⌘⌥⇧P - Supports macOS 10.12 (Sierra) and later -## Screenshots +## macOS Screenshots + +![PiBar for macOS Screenshots](/.github/screenshots.jpg?raw=true) -![PiBar Screenshots](/.github/screenshots.jpg?raw=true) +## iOS Screenshots + +... ## Download +### macOS - [Download PiBar v1.1 for macOS 10.12 and later](https://s3.amazonaws.com/amiantos/PiBar-1.1.zip) - Or, [Purchase PiBar on the App Store](https://apps.apple.com/us/app/pibar-for-pi-hole/id1514292645?ls=1) for automatic updates. -## Quick Start +### iOS & iPadOS +- [Beta test pre-release versions on TestFlight](#) +- Or, [Purchase PiBar on the App Store](#) to show your support + +⚠️ **Note**: PiBar is a Universal Purchase on the App Store, you get all available platforms in one purchase. + +## macOS Quick Start 1. Launch PiBar 2. Click on the PiBar icon in your menu bar and go to Preferences @@ -46,7 +69,7 @@ PiBar gives you all the tools you need to manage your [Pi-hole](https://pi-hole. ## Credits -- PiBar was built by [Brad Root](https://github.com/amiantos) +- PiBar was designed and built by [Brad Root](https://github.com/amiantos) - PiBar's wonderful icon was designed by [Jozef Bañuelos](https://jozef.design) - Pi-hole® is a registered trademark of Pi-hole LLC. - PiBar is an independent project and is not directly affiliated with Pi-hole LLC or the Pi-hole community.