Chart.swift 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  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 ChartXValueFormatter: IAxisValueFormatter {
  11. func stringForValue(_ value: Double, axis: AxisBase?) -> String {
  12. let dateFormatter = DateFormatter()
  13. //let timezoneOffset = TimeZone.current.secondsFromGMT()
  14. //let epochTimezoneOffset = value + Double(timezoneOffset)
  15. dateFormatter.setLocalizedDateFormatFromTemplate("hh:mm")
  16. //let date = Date(timeIntervalSince1970: epochTimezoneOffset)
  17. let date = Date(timeIntervalSince1970: value)
  18. let formattedDate = dateFormatter.string(from: date)
  19. return formattedDate
  20. }
  21. }
  22. class ChartMarker: MarkerView {
  23. private var text = String()
  24. private let drawAttributes: [NSAttributedString.Key: Any] = [
  25. .font: UIFont.systemFont(ofSize: 15),
  26. //.foregroundColor: UIColor.white,
  27. //.backgroundColor: UIColor.darkGray
  28. .foregroundColor: UIColor.label,
  29. .backgroundColor: UIColor.secondarySystemBackground
  30. ]
  31. override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
  32. if entry.data != nil {
  33. text = entry.data as? String ?? ""
  34. } else {
  35. text = String(entry.y)
  36. }
  37. }
  38. override func draw(context: CGContext, point: CGPoint) {
  39. super.draw(context: context, point: point)
  40. let sizeForDrawing = text.size(withAttributes: drawAttributes)
  41. bounds.size = sizeForDrawing
  42. offset = CGPoint(x: -sizeForDrawing.width / 2, y: -sizeForDrawing.height - 4)
  43. let offset = offsetForDrawing(atPoint: point)
  44. let originPoint = CGPoint(x: point.x + offset.x, y: point.y + offset.y)
  45. let rectForText = CGRect(origin: originPoint, size: sizeForDrawing)
  46. drawText(text: text, rect: rectForText, withAttributes: drawAttributes)
  47. }
  48. private func drawText(text: String, rect: CGRect, withAttributes attributes: [NSAttributedString.Key: Any]? = nil) {
  49. let size = bounds.size
  50. let centeredRect = CGRect(
  51. x: rect.origin.x + (rect.size.width - size.width) / 2,
  52. y: rect.origin.y + (rect.size.height - size.height) / 2,
  53. width: size.width,
  54. height: size.height
  55. )
  56. text.draw(in: centeredRect, withAttributes: attributes)
  57. }
  58. }
  59. class PillMarker: MarkerImage {
  60. private (set) var color: UIColor
  61. private (set) var font: UIFont
  62. private (set) var textColor: UIColor
  63. private var labelText: String = ""
  64. private var attrs: [NSAttributedString.Key: AnyObject]!
  65. static let formatter: DateComponentsFormatter = {
  66. let f = DateComponentsFormatter()
  67. f.allowedUnits = [.minute, .second]
  68. f.unitsStyle = .short
  69. return f
  70. }()
  71. init(color: UIColor, font: UIFont, textColor: UIColor) {
  72. self.color = color
  73. self.font = font
  74. self.textColor = textColor
  75. let paragraphStyle = NSMutableParagraphStyle()
  76. paragraphStyle.alignment = .center
  77. attrs = [.font: font, .paragraphStyle: paragraphStyle, .foregroundColor: textColor, .baselineOffset: NSNumber(value: -4)]
  78. super.init()
  79. }
  80. override func draw(context: CGContext, point: CGPoint) {
  81. // custom padding around text
  82. let labelWidth = labelText.size(withAttributes: attrs).width + 10
  83. // if you modify labelHeigh you will have to tweak baselineOffset in attrs
  84. let labelHeight = labelText.size(withAttributes: attrs).height + 4
  85. // place pill above the marker, centered along x
  86. var rectangle = CGRect(x: point.x, y: point.y, width: labelWidth, height: labelHeight)
  87. rectangle.origin.x -= rectangle.width / 2.0
  88. let spacing: CGFloat = 20
  89. rectangle.origin.y -= rectangle.height + spacing
  90. // rounded rect
  91. let clipPath = UIBezierPath(roundedRect: rectangle, cornerRadius: 6.0).cgPath
  92. context.addPath(clipPath)
  93. context.setFillColor(UIColor.secondarySystemBackground.cgColor)
  94. context.setStrokeColor(UIColor.label.cgColor)
  95. context.closePath()
  96. context.drawPath(using: .fillStroke)
  97. // add the text
  98. labelText.draw(with: rectangle, options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
  99. }
  100. override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
  101. if entry.data != nil {
  102. labelText = entry.data as? String ?? ""
  103. } else {
  104. labelText = String(entry.y)
  105. }
  106. }
  107. private func customString(_ value: Double) -> String {
  108. let formattedString = PillMarker.formatter.string(from: TimeInterval(value))!
  109. // using this to convert the left axis values formatting, ie 2 min
  110. return "\(formattedString)"
  111. }
  112. }