Chart.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. //
  2. // Chart.swift
  3. // LoopFollow
  4. //
  5. // Created by Jon Fawcett on 6/3/20.
  6. // Copyright © 2020 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. import Charts
  10. final class OverrideFillFormatter: IFillFormatter {
  11. func getFillLinePosition(dataSet: ILineChartDataSet, dataProvider: LineChartDataProvider) -> CGFloat {
  12. return 375
  13. }
  14. }
  15. final class CarbFillFormatter: IFillFormatter {
  16. func getFillLinePosition(dataSet: ILineChartDataSet, dataProvider: LineChartDataProvider) -> CGFloat {
  17. return 315
  18. }
  19. }
  20. final class BolusFillFormatter: IFillFormatter {
  21. func getFillLinePosition(dataSet: ILineChartDataSet, dataProvider: LineChartDataProvider) -> CGFloat {
  22. return 345
  23. }
  24. }
  25. final class basalFillFormatter: IFillFormatter {
  26. func getFillLinePosition(dataSet: ILineChartDataSet, dataProvider: LineChartDataProvider) -> CGFloat {
  27. return 0
  28. }
  29. }
  30. final class ChartXValueFormatter: IAxisValueFormatter {
  31. func stringForValue(_ value: Double, axis: AxisBase?) -> String {
  32. let dateFormatter = DateFormatter()
  33. //let timezoneOffset = TimeZone.current.secondsFromGMT()
  34. //let epochTimezoneOffset = value + Double(timezoneOffset)
  35. if dateTimeUtils.is24Hour() {
  36. dateFormatter.setLocalizedDateFormatFromTemplate("HH:mm")
  37. } else {
  38. dateFormatter.setLocalizedDateFormatFromTemplate("hh:mm")
  39. }
  40. //let date = Date(timeIntervalSince1970: epochTimezoneOffset)
  41. let date = Date(timeIntervalSince1970: value)
  42. let formattedDate = dateFormatter.string(from: date)
  43. return formattedDate
  44. }
  45. }
  46. final class ChartYDataValueFormatter: IValueFormatter {
  47. func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
  48. if entry.data != nil {
  49. return entry.data as? String ?? ""
  50. } else {
  51. return ""
  52. }
  53. }
  54. }
  55. final class ChartYOverrideValueFormatter: IValueFormatter {
  56. func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
  57. if entry.data != nil {
  58. return entry.data as? String ?? ""
  59. } else {
  60. return ""
  61. }
  62. }
  63. }
  64. final class ChartYMMOLValueFormatter: IAxisValueFormatter {
  65. func stringForValue(_ value: Double, axis: AxisBase?) -> String {
  66. return bgUnits.toDisplayUnits(String(value))
  67. }
  68. }
  69. class ChartMarker: MarkerView {
  70. private var text = String()
  71. private let drawAttributes: [NSAttributedString.Key: Any] = [
  72. .font: UIFont.systemFont(ofSize: 15),
  73. //.foregroundColor: UIColor.white,
  74. //.backgroundColor: UIColor.darkGray
  75. .foregroundColor: UIColor.label,
  76. .backgroundColor: UIColor.secondarySystemBackground
  77. ]
  78. override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
  79. if entry.data != nil {
  80. text = entry.data as? String ?? ""
  81. } else {
  82. text = String(entry.y)
  83. }
  84. }
  85. override func draw(context: CGContext, point: CGPoint) {
  86. super.draw(context: context, point: point)
  87. let sizeForDrawing = text.size(withAttributes: drawAttributes)
  88. bounds.size = sizeForDrawing
  89. offset = CGPoint(x: -sizeForDrawing.width / 2, y: -sizeForDrawing.height - 4)
  90. let offset = offsetForDrawing(atPoint: point)
  91. let originPoint = CGPoint(x: point.x + offset.x, y: point.y + offset.y)
  92. let rectForText = CGRect(origin: originPoint, size: sizeForDrawing)
  93. drawText(text: text, rect: rectForText, withAttributes: drawAttributes)
  94. }
  95. private func drawText(text: String, rect: CGRect, withAttributes attributes: [NSAttributedString.Key: Any]? = nil) {
  96. let size = bounds.size
  97. let centeredRect = CGRect(
  98. x: rect.origin.x + (rect.size.width - size.width) / 2,
  99. y: rect.origin.y + (rect.size.height - size.height) / 2,
  100. width: size.width,
  101. height: size.height
  102. )
  103. text.draw(in: centeredRect, withAttributes: attributes)
  104. }
  105. }
  106. class PillMarker: MarkerImage {
  107. private (set) var color: UIColor
  108. private (set) var font: UIFont
  109. private (set) var textColor: UIColor
  110. private var labelText: String = ""
  111. private var attrs: [NSAttributedString.Key: AnyObject]!
  112. static let formatter: DateComponentsFormatter = {
  113. let f = DateComponentsFormatter()
  114. f.allowedUnits = [.minute, .second]
  115. f.unitsStyle = .short
  116. return f
  117. }()
  118. init(color: UIColor, font: UIFont, textColor: UIColor) {
  119. self.color = color
  120. self.font = font
  121. self.textColor = textColor
  122. let paragraphStyle = NSMutableParagraphStyle()
  123. paragraphStyle.alignment = .center
  124. attrs = [.font: font, .paragraphStyle: paragraphStyle, .foregroundColor: textColor, .baselineOffset: NSNumber(value: -4)]
  125. super.init()
  126. }
  127. override func draw(context: CGContext, point: CGPoint) {
  128. // custom padding around text
  129. let labelWidth = labelText.size(withAttributes: attrs).width + 10
  130. // if you modify labelHeigh you will have to tweak baselineOffset in attrs
  131. let labelHeight = labelText.size(withAttributes: attrs).height + 4
  132. // place pill above the marker, centered along x
  133. var rectangle = CGRect(x: point.x, y: point.y, width: labelWidth, height: labelHeight)
  134. rectangle.origin.x -= rectangle.width / 2.0
  135. var spacing: CGFloat = 20
  136. if point.y < 300 { spacing = -40 }
  137. rectangle.origin.y -= rectangle.height + spacing
  138. // rounded rect
  139. let clipPath = UIBezierPath(roundedRect: rectangle, cornerRadius: 6.0).cgPath
  140. context.addPath(clipPath)
  141. context.setFillColor(UIColor.secondarySystemBackground.cgColor)
  142. context.setStrokeColor(UIColor.label.cgColor)
  143. context.closePath()
  144. context.drawPath(using: .fillStroke)
  145. // add the text
  146. labelText.draw(with: rectangle, options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
  147. }
  148. override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
  149. if entry.data != nil {
  150. //var multiplier = entry.data as! Double * 100.0
  151. //labelText = String(format: "%.0f%%", multiplier)
  152. labelText = entry.data as? String ?? ""
  153. } else {
  154. labelText = String(entry.y)
  155. }
  156. }
  157. private func customString(_ value: Double) -> String {
  158. let formattedString = PillMarker.formatter.string(from: TimeInterval(value))!
  159. // using this to convert the left axis values formatting, ie 2 min
  160. return "\(formattedString)"
  161. }
  162. }