AxisBase.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. //
  2. // AxisBase.swift
  3. // Charts
  4. //
  5. // Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
  6. // A port of MPAndroidChart for iOS
  7. // Licensed under Apache License 2.0
  8. //
  9. // https://github.com/danielgindi/Charts
  10. //
  11. import Foundation
  12. import CoreGraphics
  13. /// Base class for all axes
  14. @objc(ChartAxisBase)
  15. open class AxisBase: ComponentBase
  16. {
  17. public override init()
  18. {
  19. super.init()
  20. }
  21. /// Custom formatter that is used instead of the auto-formatter if set
  22. private lazy var _axisValueFormatter: AxisValueFormatter = DefaultAxisValueFormatter(decimals: decimals)
  23. @objc open var labelFont = NSUIFont.systemFont(ofSize: 10.0)
  24. @objc open var labelTextColor = NSUIColor.labelOrBlack
  25. @objc open var axisLineColor = NSUIColor.gray
  26. @objc open var axisLineWidth = CGFloat(0.5)
  27. @objc open var axisLineDashPhase = CGFloat(0.0)
  28. @objc open var axisLineDashLengths: [CGFloat]!
  29. @objc open var gridColor = NSUIColor.gray.withAlphaComponent(0.9)
  30. @objc open var gridLineWidth = CGFloat(0.5)
  31. @objc open var gridLineDashPhase = CGFloat(0.0)
  32. @objc open var gridLineDashLengths: [CGFloat]!
  33. @objc open var gridLineCap = CGLineCap.butt
  34. @objc open var drawGridLinesEnabled = true
  35. @objc open var drawAxisLineEnabled = true
  36. /// flag that indicates of the labels of this axis should be drawn or not
  37. @objc open var drawLabelsEnabled = true
  38. private var _centerAxisLabelsEnabled = false
  39. /// Centers the axis labels instead of drawing them at their original position.
  40. /// This is useful especially for grouped BarChart.
  41. @objc open var centerAxisLabelsEnabled: Bool
  42. {
  43. get { return _centerAxisLabelsEnabled && entryCount > 0 }
  44. set { _centerAxisLabelsEnabled = newValue }
  45. }
  46. @objc open var isCenterAxisLabelsEnabled: Bool
  47. {
  48. get { return centerAxisLabelsEnabled }
  49. }
  50. /// array of limitlines that can be set for the axis
  51. private var _limitLines = [ChartLimitLine]()
  52. /// Are the LimitLines drawn behind the data or in front of the data?
  53. ///
  54. /// **default**: false
  55. @objc open var drawLimitLinesBehindDataEnabled = false
  56. /// Are the grid lines drawn behind the data or in front of the data?
  57. ///
  58. /// **default**: true
  59. @objc open var drawGridLinesBehindDataEnabled = true
  60. /// the flag can be used to turn off the antialias for grid lines
  61. @objc open var gridAntialiasEnabled = true
  62. /// the actual array of entries
  63. @objc open var entries = [Double]()
  64. /// axis label entries only used for centered labels
  65. @objc open var centeredEntries = [Double]()
  66. /// the number of entries the legend contains
  67. @objc open var entryCount: Int { return entries.count }
  68. /// the number of label entries the axis should have
  69. ///
  70. /// **default**: 6
  71. private var _labelCount = Int(6)
  72. /// the number of decimal digits to use (for the default formatter
  73. @objc open var decimals: Int = 0
  74. /// When true, axis labels are controlled by the `granularity` property.
  75. /// When false, axis values could possibly be repeated.
  76. /// This could happen if two adjacent axis values are rounded to same value.
  77. /// If using granularity this could be avoided by having fewer axis values visible.
  78. @objc open var granularityEnabled = false
  79. private var _granularity = Double(1.0)
  80. /// The minimum interval between axis values.
  81. /// This can be used to avoid label duplicating when zooming in.
  82. ///
  83. /// **default**: 1.0
  84. @objc open var granularity: Double
  85. {
  86. get
  87. {
  88. return _granularity
  89. }
  90. set
  91. {
  92. _granularity = newValue
  93. // set this to `true` if it was disabled, as it makes no sense to set this property with granularity disabled
  94. granularityEnabled = true
  95. }
  96. }
  97. public var axisMinValue: Double
  98. {
  99. get
  100. {
  101. return _axisMinimum
  102. }
  103. set
  104. {
  105. _customAxisMin = true
  106. _axisMinimum = newValue
  107. }
  108. }
  109. /// The maximum value for this axis.
  110. /// If set, this value will not be calculated automatically depending on the provided data.
  111. /// Use `resetCustomAxisMin()` to undo this.
  112. public var axisMaxValue: Double
  113. {
  114. get
  115. {
  116. return _axisMaximum
  117. }
  118. set
  119. {
  120. _customAxisMax = true
  121. _axisMaximum = newValue
  122. }
  123. }
  124. /// The minimum interval between axis values.
  125. @objc open var isGranularityEnabled: Bool
  126. {
  127. get
  128. {
  129. return granularityEnabled
  130. }
  131. }
  132. /// if true, the set number of y-labels will be forced
  133. @objc open var forceLabelsEnabled = false
  134. @objc open func getLongestLabel() -> String
  135. {
  136. var longest = ""
  137. for i in entries.indices
  138. {
  139. let text = getFormattedLabel(i)
  140. if longest.count < text.count
  141. {
  142. longest = text
  143. }
  144. }
  145. return longest
  146. }
  147. /// - Returns: The formatted label at the specified index. This will either use the auto-formatter or the custom formatter (if one is set).
  148. @objc open func getFormattedLabel(_ index: Int) -> String
  149. {
  150. guard entries.indices.contains(index) else { return "" }
  151. return valueFormatter?.stringForValue(entries[index], axis: self) ?? ""
  152. }
  153. /// Sets the formatter to be used for formatting the axis labels.
  154. /// 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.
  155. /// Use `nil` to use the formatter calculated by the chart.
  156. @objc open var valueFormatter: AxisValueFormatter?
  157. {
  158. get
  159. {
  160. if _axisValueFormatter is DefaultAxisValueFormatter &&
  161. (_axisValueFormatter as! DefaultAxisValueFormatter).hasAutoDecimals &&
  162. (_axisValueFormatter as! DefaultAxisValueFormatter).decimals != decimals
  163. {
  164. (self._axisValueFormatter as! DefaultAxisValueFormatter).decimals = self.decimals
  165. }
  166. return _axisValueFormatter
  167. }
  168. set
  169. {
  170. _axisValueFormatter = newValue ?? DefaultAxisValueFormatter(decimals: decimals)
  171. }
  172. }
  173. @objc open var isDrawGridLinesEnabled: Bool { return drawGridLinesEnabled }
  174. @objc open var isDrawAxisLineEnabled: Bool { return drawAxisLineEnabled }
  175. @objc open var isDrawLabelsEnabled: Bool { return drawLabelsEnabled }
  176. /// Are the LimitLines drawn behind the data or in front of the data?
  177. ///
  178. /// **default**: false
  179. @objc open var isDrawLimitLinesBehindDataEnabled: Bool { return drawLimitLinesBehindDataEnabled }
  180. /// Are the grid lines drawn behind the data or in front of the data?
  181. ///
  182. /// **default**: true
  183. @objc open var isDrawGridLinesBehindDataEnabled: Bool { return drawGridLinesBehindDataEnabled }
  184. /// Extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum`
  185. @objc open var spaceMin: Double = 0.0
  186. /// Extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum`
  187. @objc open var spaceMax: Double = 0.0
  188. /// Flag indicating that the axis-min value has been customized
  189. internal var _customAxisMin: Bool = false
  190. /// Flag indicating that the axis-max value has been customized
  191. internal var _customAxisMax: Bool = false
  192. /// Do not touch this directly, instead, use axisMinimum.
  193. /// This is automatically calculated to represent the real min value,
  194. /// and is used when calculating the effective minimum.
  195. internal var _axisMinimum = Double(0)
  196. /// Do not touch this directly, instead, use axisMaximum.
  197. /// This is automatically calculated to represent the real max value,
  198. /// and is used when calculating the effective maximum.
  199. internal var _axisMaximum = Double(0)
  200. /// the total range of values this axis covers
  201. @objc open var axisRange = Double(0)
  202. /// The minumum number of labels on the axis
  203. @objc open var axisMinLabels = Int(2) {
  204. didSet { axisMinLabels = axisMinLabels > 0 ? axisMinLabels : oldValue }
  205. }
  206. /// The maximum number of labels on the axis
  207. @objc open var axisMaxLabels = Int(25) {
  208. didSet { axisMaxLabels = axisMaxLabels > 0 ? axisMaxLabels : oldValue }
  209. }
  210. /// the number of label entries the axis should have
  211. /// max = 25,
  212. /// min = 2,
  213. /// default = 6,
  214. /// be aware that this number is not fixed and can only be approximated
  215. @objc open var labelCount: Int
  216. {
  217. get
  218. {
  219. return _labelCount
  220. }
  221. set
  222. {
  223. let range = axisMinLabels...axisMaxLabels as ClosedRange
  224. _labelCount = newValue.clamped(to: range)
  225. forceLabelsEnabled = false
  226. }
  227. }
  228. @objc open func setLabelCount(_ count: Int, force: Bool)
  229. {
  230. self.labelCount = count
  231. forceLabelsEnabled = force
  232. }
  233. /// `true` if focing the y-label count is enabled. Default: false
  234. @objc open var isForceLabelsEnabled: Bool { return forceLabelsEnabled }
  235. /// Adds a new ChartLimitLine to this axis.
  236. @objc open func addLimitLine(_ line: ChartLimitLine)
  237. {
  238. _limitLines.append(line)
  239. }
  240. /// Removes the specified ChartLimitLine from the axis.
  241. @objc open func removeLimitLine(_ line: ChartLimitLine)
  242. {
  243. guard let i = _limitLines.firstIndex(of: line) else { return }
  244. _limitLines.remove(at: i)
  245. }
  246. /// Removes all LimitLines from the axis.
  247. @objc open func removeAllLimitLines()
  248. {
  249. _limitLines.removeAll(keepingCapacity: false)
  250. }
  251. /// The LimitLines of this axis.
  252. @objc open var limitLines : [ChartLimitLine]
  253. {
  254. return _limitLines
  255. }
  256. // MARK: Custom axis ranges
  257. /// By calling this method, any custom minimum value that has been previously set is reseted, and the calculation is done automatically.
  258. @objc open func resetCustomAxisMin()
  259. {
  260. _customAxisMin = false
  261. }
  262. @objc open var isAxisMinCustom: Bool { return _customAxisMin }
  263. /// By calling this method, any custom maximum value that has been previously set is reseted, and the calculation is done automatically.
  264. @objc open func resetCustomAxisMax()
  265. {
  266. _customAxisMax = false
  267. }
  268. @objc open var isAxisMaxCustom: Bool { return _customAxisMax }
  269. /// The minimum value for this axis.
  270. /// If set, this value will not be calculated automatically depending on the provided data.
  271. /// Use `resetCustomAxisMin()` to undo this.
  272. @objc open var axisMinimum: Double
  273. {
  274. get
  275. {
  276. return _axisMinimum
  277. }
  278. set
  279. {
  280. _customAxisMin = true
  281. _axisMinimum = newValue
  282. axisRange = abs(_axisMaximum - newValue)
  283. }
  284. }
  285. /// The maximum value for this axis.
  286. /// If set, this value will not be calculated automatically depending on the provided data.
  287. /// Use `resetCustomAxisMax()` to undo this.
  288. @objc open var axisMaximum: Double
  289. {
  290. get
  291. {
  292. return _axisMaximum
  293. }
  294. set
  295. {
  296. _customAxisMax = true
  297. _axisMaximum = newValue
  298. axisRange = abs(newValue - _axisMinimum)
  299. }
  300. }
  301. /// Calculates the minimum, maximum and range values of the YAxis with the given minimum and maximum values from the chart data.
  302. ///
  303. /// - Parameters:
  304. /// - dataMin: the y-min value according to chart data
  305. /// - dataMax: the y-max value according to chart
  306. @objc open func calculate(min dataMin: Double, max dataMax: Double)
  307. {
  308. // if custom, use value as is, else use data value
  309. var min = _customAxisMin ? _axisMinimum : (dataMin - spaceMin)
  310. var max = _customAxisMax ? _axisMaximum : (dataMax + spaceMax)
  311. // temporary range (before calculations)
  312. let range = abs(max - min)
  313. // in case all values are equal
  314. if range == 0.0
  315. {
  316. max = max + 1.0
  317. min = min - 1.0
  318. }
  319. _axisMinimum = min
  320. _axisMaximum = max
  321. // actual range
  322. axisRange = abs(max - min)
  323. }
  324. }