| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- // LoopFollow
- // Chart.swift
- import Charts
- import Foundation
- final class OverrideFillFormatter: FillFormatter {
- func getFillLinePosition(dataSet: Charts.LineChartDataSetProtocol, dataProvider _: Charts.LineChartDataProvider) -> CGFloat {
- return CGFloat(dataSet.entryForIndex(0)!.y)
- // return 375
- }
- }
- final class basalFillFormatter: FillFormatter {
- func getFillLinePosition(dataSet _: Charts.LineChartDataSetProtocol, dataProvider _: Charts.LineChartDataProvider) -> CGFloat {
- return 0
- }
- }
- final class ChartXValueFormatter: AxisValueFormatter {
- func stringForValue(_ value: Double, axis _: AxisBase?) -> String {
- let dateFormatter = DateFormatter()
- // let timezoneOffset = TimeZone.current.secondsFromGMT()
- // let epochTimezoneOffset = value + Double(timezoneOffset)
- if dateTimeUtils.is24Hour() {
- dateFormatter.setLocalizedDateFormatFromTemplate("HH:mm")
- } else {
- dateFormatter.setLocalizedDateFormatFromTemplate("hh:mm")
- }
- if Storage.shared.graphTimeZoneEnabled.value,
- let tz = TimeZone(identifier: Storage.shared.graphTimeZoneIdentifier.value)
- {
- dateFormatter.timeZone = tz
- }
- // let date = Date(timeIntervalSince1970: epochTimezoneOffset)
- let date = Date(timeIntervalSince1970: value)
- let formattedDate = dateFormatter.string(from: date)
- return formattedDate
- }
- }
- final class ChartYDataValueFormatter: ValueFormatter {
- func stringForValue(_: Double, entry: ChartDataEntry, dataSetIndex _: Int, viewPortHandler _: ViewPortHandler?) -> String {
- guard let text = entry.data as? String else { return "" }
- // Treatment entries store "label\r\rpillText" — extract only the label portion.
- if let range = text.range(of: "\r\r") {
- return String(text[..<range.lowerBound])
- }
- return text
- }
- }
- final class ChartYOverrideValueFormatter: ValueFormatter {
- func stringForValue(_: Double, entry: ChartDataEntry, dataSetIndex _: Int, viewPortHandler _: ViewPortHandler?) -> String {
- if entry.data != nil {
- return entry.data as? String ?? ""
- } else {
- return ""
- }
- }
- }
- final class ChartYMMOLValueFormatter: AxisValueFormatter {
- func stringForValue(_ value: Double, axis _: AxisBase?) -> String {
- return Localizer.toDisplayUnits(String(value))
- }
- }
- class PillMarker: MarkerImage {
- private(set) var color: UIColor
- private(set) var font: UIFont
- private(set) var textColor: UIColor
- private var labelText: String = ""
- private var attrs: [NSAttributedString.Key: AnyObject]!
- static let formatter: DateComponentsFormatter = {
- let f = DateComponentsFormatter()
- f.allowedUnits = [.minute, .second]
- f.unitsStyle = .short
- return f
- }()
- init(color: UIColor, font: UIFont, textColor: UIColor) {
- self.color = color
- self.font = font
- self.textColor = textColor
- let paragraphStyle = NSMutableParagraphStyle()
- paragraphStyle.alignment = .center
- attrs = [.font: font, .paragraphStyle: paragraphStyle, .foregroundColor: textColor, .baselineOffset: NSNumber(value: -4)]
- super.init()
- }
- override func draw(context: CGContext, point: CGPoint) {
- // custom padding around text
- let labelWidth = labelText.size(withAttributes: attrs).width + 10
- // if you modify labelHeigh you will have to tweak baselineOffset in attrs
- let labelHeight = labelText.size(withAttributes: attrs).height + 4
- // place pill above the marker, centered along x
- var rectangle = CGRect(x: point.x, y: point.y, width: labelWidth, height: labelHeight)
- rectangle.origin.x -= rectangle.width / 2.0
- var spacing: CGFloat = 20
- if point.y < 300 { spacing = -40 }
- rectangle.origin.y -= rectangle.height + spacing
- // rounded rect
- let clipPath = UIBezierPath(roundedRect: rectangle, cornerRadius: 6.0).cgPath
- context.addPath(clipPath)
- context.setFillColor(UIColor.secondarySystemBackground.cgColor)
- context.setStrokeColor(UIColor.label.cgColor)
- context.closePath()
- context.drawPath(using: .fillStroke)
- // add the text
- labelText.draw(with: rectangle, options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
- }
- private static let pillSeparator = "\r\r"
- override func refreshContent(entry: ChartDataEntry, highlight _: Highlight) {
- if let text = entry.data as? String {
- // Treatment entries use pillSeparator to separate the value label from the marker text.
- if let range = text.range(of: Self.pillSeparator) {
- labelText = String(text[range.upperBound...])
- } else {
- labelText = text
- }
- } else {
- labelText = String(entry.y)
- }
- }
- private func customString(_ value: Double) -> String {
- let formattedString = PillMarker.formatter.string(from: TimeInterval(value))!
- // using this to convert the left axis values formatting, ie 2 min
- return "\(formattedString)"
- }
- }
- // MARK: - Cone of Uncertainty
- class ConeChartDataEntry: ChartDataEntry {
- var yMin: Double = 0.0
- var yMax: Double = 0.0
- required init() {
- super.init()
- }
- init(x: Double, yMin: Double, yMax: Double) {
- self.yMin = yMin
- self.yMax = yMax
- super.init(x: x, y: yMax)
- }
- override func copy(with _: NSZone? = nil) -> Any {
- let copy = ConeChartDataEntry(x: x, yMin: yMin, yMax: yMax)
- copy.data = data
- return copy
- }
- }
- class ConeOfUncertaintyRenderer: LineChartRenderer {
- let coneDataSetIndex: Int
- init(dataProvider: LineChartDataProvider?, animator: Animator?, viewPortHandler: ViewPortHandler?, coneDataSetIndex: Int) {
- self.coneDataSetIndex = coneDataSetIndex
- super.init(dataProvider: dataProvider!, animator: animator!, viewPortHandler: viewPortHandler!)
- }
- override func drawExtras(context: CGContext) {
- super.drawExtras(context: context)
- guard let dataProvider = dataProvider,
- dataProvider.lineData?.dataSets.count ?? 0 > coneDataSetIndex,
- let lineDataSet = dataProvider.lineData?.dataSets[coneDataSetIndex] as? LineChartDataSet,
- lineDataSet.entryCount > 1 else { return }
- let trans = dataProvider.getTransformer(forAxis: lineDataSet.axisDependency)
- let phaseY = animator.phaseY
- var upperPoints = [CGPoint]()
- var lowerPoints = [CGPoint]()
- for i in 0 ..< lineDataSet.entryCount {
- guard let entry = lineDataSet.entryForIndex(i) as? ConeChartDataEntry else { continue }
- upperPoints.append(trans.pixelForValues(x: entry.x, y: entry.yMax * phaseY))
- lowerPoints.append(trans.pixelForValues(x: entry.x, y: entry.yMin * phaseY))
- }
- guard upperPoints.count > 1 else { return }
- context.saveGState()
- let path = CGMutablePath()
- path.move(to: upperPoints[0])
- for i in 1 ..< upperPoints.count {
- path.addLine(to: upperPoints[i])
- }
- for i in stride(from: lowerPoints.count - 1, through: 0, by: -1) {
- path.addLine(to: lowerPoints[i])
- }
- path.closeSubpath()
- context.addPath(path)
- context.setFillColor(UIColor.systemBlue.withAlphaComponent(0.4).cgColor)
- context.fillPath()
- context.restoreGState()
- }
- }
|