-
Notifications
You must be signed in to change notification settings - Fork 644
Expand file tree
/
Copy pathLineView.swift
More file actions
143 lines (135 loc) · 6.38 KB
/
LineView.swift
File metadata and controls
143 lines (135 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//
// LineView.swift
// LineChart
//
// Created by András Samu on 2019. 09. 02..
// Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI
public struct LineView: View {
@ObservedObject var data: ChartData
public var xAxisLables: [String]?
public var title: String?
public var legend: String?
public var showOrigin: Bool?
public var style: ChartStyle
public var darkModeStyle: ChartStyle
public var valueSpecifier: String
@Environment(\.colorScheme) var colorScheme: ColorScheme
@State private var showLegend = false
@State private var dragLocation: CGPoint = .zero
@State private var indicatorLocation: CGPoint = .zero
@State private var closestPoint: CGPoint = .zero
@State private var opacity: Double = 0
@State private var currentDataNumber: Double = 0
@State private var currentDataLabel: String = ""
@State private var hideHorizontalLines: Bool = false
public init(data: [Double],
xAxisLables: [String]? = [],
title: String? = nil,
legend: String? = nil,
showOrigin: Bool? = true,
style: ChartStyle = Styles.lineChartStyleOne,
valueSpecifier: String? = "%.1f") {
self.data = ChartData(points: data)
self.xAxisLables = xAxisLables
self.title = title
self.legend = legend
self.showOrigin = showOrigin
self.style = style
self.valueSpecifier = valueSpecifier!
self.darkModeStyle = style.darkModeStyle != nil ? style.darkModeStyle! : Styles.lineViewDarkMode
}
public var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 8) {
Group {
if self.title != nil {
Text(self.title!)
.font(.title)
.bold().foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.textColor : self.style.textColor)
}
if self.legend != nil {
Text(self.legend!)
.font(.callout)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
}
}
ZStack {
GeometryReader { reader in
Rectangle()
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.backgroundColor : self.style.backgroundColor)
if self.showLegend {
Legend(data: self.data,
frame: .constant(reader.frame(in: .local)), hideHorizontalLines: self.$hideHorizontalLines, showOrigin: self.showOrigin)
.transition(.opacity)
.animation(Animation.easeOut(duration: 1).delay(1))
}
Line(data: self.data,
frame: .constant(CGRect(x: 0, y: 0, width: reader.frame(in: .local).width - 32, height: reader.frame(in: .local).height)),
touchLocation: self.$indicatorLocation,
showIndicator: self.$hideHorizontalLines,
minDataValue: .constant(nil),
maxDataValue: .constant(nil),
showBackground: false)
.offset(x: 32, y: 0)
.onAppear {
self.showLegend = true
}
.onDisappear {
self.showLegend = false
}
}
.frame(width: geometry.frame(in: .local).size.width, height: 240)
MagnifierRect(currentNumber: self.$currentDataNumber, currentDataLabel: self.$currentDataLabel, valueSpecifier: self.valueSpecifier)
.opacity(self.opacity)
.offset(x: self.dragLocation.x - geometry.frame(in: .local).size.width / 2, y: 32)
}
.frame(width: geometry.frame(in: .local).size.width, height: 240)
.gesture(DragGesture()
.onChanged { value in
self.dragLocation = value.location
self.indicatorLocation = CGPoint(x: max(value.location.x - 30, 0), y: 32)
self.opacity = 1
self.closestPoint = self.getClosestDataPoint(toPoint: value.location, width: geometry.frame(in: .local).size.width - 30, height: 240)
self.hideHorizontalLines = true
}
.onEnded { _ in
self.opacity = 0
self.hideHorizontalLines = false
}
)
if self.xAxisLables != nil {
HStack{
ForEach(self.xAxisLables!, id: \.self) { xAxisLable in
Text(xAxisLable)
.foregroundColor(Colors.LegendText)
.font(.caption)
}
}
.padding(.trailing, 32)
.offset(x: 32, y: self.showOrigin == true ? 26 : 0)
}
}
}
}
func getClosestDataPoint(toPoint: CGPoint, width: CGFloat, height: CGFloat) -> CGPoint {
let points = self.data.onlyPoints()
let stepWidth: CGFloat = width / CGFloat(points.count - 1)
let stepHeight: CGFloat = height / CGFloat(points.max()! + points.min()!)
let index: Int = Int(floor((toPoint.x - 15) / stepWidth))
if let xAxisLables = xAxisLables, index >= 0 && index < xAxisLables.count {
self.currentDataLabel = xAxisLables[index]
}
if index >= 0 && index < points.count {
self.currentDataNumber = points[index]
return CGPoint(x: CGFloat(index) * stepWidth, y: CGFloat(points[index]) * stepHeight)
}
return .zero
}
}
struct LineView_Previews: PreviewProvider {
static var previews: some View {
LineView(data: [8, 23, 54, 32, 12, 37, 7, 23, 43], xAxisLables: [], title: "Full chart", legend: "Full chart", showOrigin: true, style: Styles.lineChartStyleOne)
}
}