Compare commits

...

14 Commits

Author SHA1 Message Date
Andras Samu c12c773af0 fixed unwrap error 2020-03-17 10:38:45 +01:00
Andras Samu 75804f470a Customisable drop shadow color (#53)
* Customisable drop shadow color

* Drop shadow color parameter in ChartStyle

* Fixed BarChartView
2020-03-05 11:57:38 +01:00
Andras Samu 04989ad159 Updated ReadMe with multilinechartview 2020-03-04 14:21:42 +01:00
Andras Samu 7365bc91ef Added MultiLineChartView, fixed LineView legend disappearing on navigating back 2020-03-04 14:03:34 +01:00
Andras Samu 257e5fca30 Fixed global max and min for multiline chartview 2020-03-03 19:10:10 +01:00
Fredrik Lillejordet 6a9546bb1f added id: self in ForEach for dynamic content in BarChartRow (#49)
Co-authored-by: Andras Samu <samu.andris1@gmail.com>
2020-03-03 12:45:45 +01:00
Andrew Yang b230ed0369 Fix to use dark mode settings for barchatview label text. (#47) 2020-03-03 12:42:30 +01:00
Andras Samu a8b4101c52 Merge remote-tracking branch 'refs/remotes/origin/master' 2020-03-03 12:41:42 +01:00
Andras Samu 2a1b55f79f Adding multiline chartview and straight linechart 2020-03-03 12:41:11 +01:00
Andras Samu 47731bfeff Added cutom darkmode style description 2020-02-13 12:21:20 +01:00
Andras Samu 841bde1377 Fixed infinite size compile error 2020-02-13 12:15:28 +01:00
Andras Samu 37779e1b54 added a darkmodestyle so you can customize darkmode appearance for lineview, linechartview, barchartview 2020-02-13 11:59:21 +01:00
Andras Samu ba5bc4f861 Fixed barchart crashing for empty array 2020-02-13 11:31:49 +01:00
Andras Samu 80d546de03 fixed lineview for small negative numbers 2020-02-13 11:20:39 +01:00
13 changed files with 614 additions and 238 deletions
+30 -1
View File
@@ -27,7 +27,28 @@ Added an example project, with **iOS, watchOS** target: https://github.com/AppPe
## Line charts
**New full screen view called LineView!!!**
**LineChartView with multiple lines!**
First release of this feature, interaction is disabled for now, I'll figure it out how could be the best to interact with multiple lines with a single touch.
![Multiine Charts](./Resources/multiline1.gif "Multiine Charts")
Usage:
```swift
MultiLineChartView(data: [([8,32,11,23,40,28], GradientColors.green), ([90,99,78,111,70,60,77], GradientColors.purple), ([34,56,72,38,43,100,50], GradientColors.orngPink)], title: "Title")
```
Gradient colors are now under the `GradientColor` struct you can create your own gradient by `GradientColor(start: Color, end: Color)`
Available preset gradients:
* orange
* blue
* green
* blu
* bluPurpl
* purple
* prplPink
* prplNeon
* orngPink
**Full screen view called LineView!!!**
![Line Charts](./Resources/fullscreen2.gif "Line Charts")
@@ -39,6 +60,14 @@ Adopts to dark mode automatically
![Line Charts](./Resources/showcase3.gif "Line Charts")
You can add your custom darkmode style by specifying:
```swift
let myCustomStyle = ChartStyle(...)
let myCutsomDarkModeStyle = ChartStyle(...)
myCustomStyle.darkModeStyle = myCutsomDarkModeStyle
```
**Line chart is interactive, so you can drag across to reveal the data points**
You can add a line chart with the following code:
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

@@ -17,19 +17,14 @@ public struct BarChartCell : View {
return Double(width)/(Double(numberOfDataPoints) * 1.5)
}
var accentColor: Color
var secondGradientAccentColor: Color?
var gradientColors:[Color] {
if (secondGradientAccentColor != nil) {
return [secondGradientAccentColor!, accentColor]
}
return [accentColor, accentColor]
}
var gradient: GradientColor?
@State var scaleValue: Double = 0
@Binding var touchLocation: CGFloat
public var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 4)
.fill(LinearGradient(gradient: Gradient(colors: gradientColors), startPoint: .bottom, endPoint: .top))
.fill(LinearGradient(gradient: gradient?.getGradient() ?? GradientColor(start: accentColor, end: accentColor).getGradient(), startPoint: .bottom, endPoint: .top))
}
.frame(width: CGFloat(self.cellWidth))
.scaleEffect(CGSize(width: 1, height: self.scaleValue), anchor: .bottom)
@@ -43,7 +38,7 @@ public struct BarChartCell : View {
#if DEBUG
struct ChartCell_Previews : PreviewProvider {
static var previews: some View {
BarChartCell(value: Double(0.75), width: 320, numberOfDataPoints: 12, accentColor: Colors.OrangeStart, secondGradientAccentColor: nil, touchLocation: .constant(-1))
BarChartCell(value: Double(0.75), width: 320, numberOfDataPoints: 12, accentColor: Colors.OrangeStart, gradient: nil, touchLocation: .constant(-1))
}
}
#endif
@@ -11,7 +11,7 @@ import SwiftUI
public struct BarChartRow : View {
var data: [Double]
var accentColor: Color
var secondGradientAccentColor: Color?
var gradient: GradientColor?
var maxValue: Double {
data.max() ?? 0
}
@@ -19,8 +19,14 @@ public struct BarChartRow : View {
public var body: some View {
GeometryReader { geometry in
HStack(alignment: .bottom, spacing: (geometry.frame(in: .local).width-22)/CGFloat(self.data.count * 3)){
ForEach(0..<self.data.count) { i in
BarChartCell(value: self.normalizedValue(index: i), index: i, width: Float(geometry.frame(in: .local).width - 22), numberOfDataPoints: self.data.count, accentColor: self.accentColor, secondGradientAccentColor: self.secondGradientAccentColor, touchLocation: self.$touchLocation)
ForEach(0..<self.data.count, id: \.self) { i in
BarChartCell(value: self.normalizedValue(index: i),
index: i,
width: Float(geometry.frame(in: .local).width - 22),
numberOfDataPoints: self.data.count,
accentColor: self.accentColor,
gradient: self.gradient,
touchLocation: self.$touchLocation)
.scaleEffect(self.touchLocation > CGFloat(i)/CGFloat(self.data.count) && self.touchLocation < CGFloat(i+1)/CGFloat(self.data.count) ? CGSize(width: 1.4, height: 1.1) : CGSize(width: 1, height: 1), anchor: .bottom)
.animation(.spring())
@@ -9,10 +9,12 @@
import SwiftUI
public struct BarChartView : View {
@Environment(\.colorScheme) var colorScheme: ColorScheme
private var data: ChartData
public var title: String
public var legend: String?
public var style: ChartStyle
public var darkModeStyle: ChartStyle
public var formSize:CGSize
public var dropShadow: Bool
public var cornerImage: Image
@@ -36,6 +38,7 @@ public struct BarChartView : View {
self.title = title
self.legend = legend
self.style = style
self.darkModeStyle = style.darkModeStyle != nil ? style.darkModeStyle! : Styles.barChartStyleOrangeDark
self.formSize = form!
self.dropShadow = dropShadow!
self.cornerImage = cornerImage!
@@ -45,49 +48,58 @@ public struct BarChartView : View {
public var body: some View {
ZStack{
Rectangle()
.fill(self.style.backgroundColor)
.fill(self.colorScheme == .dark ? self.darkModeStyle.backgroundColor : self.style.backgroundColor)
.cornerRadius(20)
.shadow(color: Color.gray, radius: self.dropShadow ? 8 : 0)
.shadow(color: self.style.dropShadowColor, radius: self.dropShadow ? 8 : 0)
VStack(alignment: .leading){
HStack{
if(!showValue){
Text(self.title)
.font(.headline)
.foregroundColor(self.style.textColor)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.textColor : self.style.textColor)
}else{
Text("\(self.currentValue, specifier: self.valueSpecifier)")
.font(.headline)
.foregroundColor(self.style.textColor)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.textColor : self.style.textColor)
}
if(self.formSize == ChartForm.large && self.legend != nil && !showValue) {
Text(self.legend!)
.font(.callout)
.foregroundColor(self.style.accentColor)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.accentColor : self.style.accentColor)
.transition(.opacity)
.animation(.easeOut)
}
Spacer()
self.cornerImage
.imageScale(.large)
.foregroundColor(self.style.legendTextColor)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
}.padding()
BarChartRow(data: data.points.map{$0.1}, accentColor: self.style.accentColor, secondGradientAccentColor: self.style.secondGradientColor, touchLocation: self.$touchLocation)
BarChartRow(data: data.points.map{$0.1},
accentColor: self.colorScheme == .dark ? self.darkModeStyle.accentColor : self.style.accentColor,
gradient: self.colorScheme == .dark ? self.darkModeStyle.gradientColor : self.style.gradientColor,
touchLocation: self.$touchLocation)
if self.legend != nil && self.formSize == ChartForm.medium && !self.showLabelValue{
Text(self.legend!)
.font(.headline)
.foregroundColor(self.style.legendTextColor)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
.padding()
}else if (self.data.valuesGiven) {
LabelView(arrowOffset: self.getArrowOffset(touchLocation: self.touchLocation), title: .constant(self.getCurrentValue().0)).offset(x: self.getLabelViewOffset(touchLocation: self.touchLocation), y: -6)
}else if (self.data.valuesGiven && self.getCurrentValue() != nil) {
LabelView(arrowOffset: self.getArrowOffset(touchLocation: self.touchLocation),
title: .constant(self.getCurrentValue()!.0))
.offset(x: self.getLabelViewOffset(touchLocation: self.touchLocation), y: -6)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
}
}
}.frame(minWidth:self.formSize.width, maxWidth: self.isFullWidth ? .infinity : self.formSize.width, minHeight:self.formSize.height, maxHeight:self.formSize.height)
}.frame(minWidth:self.formSize.width,
maxWidth: self.isFullWidth ? .infinity : self.formSize.width,
minHeight:self.formSize.height,
maxHeight:self.formSize.height)
.gesture(DragGesture()
.onChanged({ value in
self.touchLocation = value.location.x/self.formSize.width
self.showValue = true
self.currentValue = self.getCurrentValue().1
self.currentValue = self.getCurrentValue()?.1 ?? 0
if(self.data.valuesGiven && self.formSize == ChartForm.medium) {
self.showLabelValue = true
}
@@ -117,7 +129,8 @@ public struct BarChartView : View {
return min(self.formSize.width-110,max(10,(self.touchLocation * self.formSize.width) - 50))
}
func getCurrentValue()-> (String,Double) {
func getCurrentValue() -> (String,Double)? {
guard self.data.points.count > 0 else { return nil}
let index = max(0,min(self.data.points.count-1,Int(floor((self.touchLocation*self.formSize.width)/(self.formSize.width/CGFloat(self.data.points.count))))))
return self.data.points[index]
}
@@ -126,7 +139,10 @@ public struct BarChartView : View {
#if DEBUG
struct ChartView_Previews : PreviewProvider {
static var previews: some View {
BarChartView(data: TestData.values ,title: "Model 3 sales", legend: "Quarterly", valueSpecifier: "%.0f")
BarChartView(data: TestData.values ,
title: "Model 3 sales",
legend: "Quarterly",
valueSpecifier: "%.0f")
}
}
#endif
+97 -23
View File
@@ -15,8 +15,8 @@ public struct Colors {
public static let color2Accent:Color = Color(hexString: "#4266E8")
public static let color3:Color = Color(hexString: "#FCECEA")
public static let color3Accent:Color = Color(hexString: "#E1614C")
public static let OrangeStart:Color = Color(hexString: "#FF782C")
public static let OrangeEnd:Color = Color(hexString: "#EC2301")
public static let OrangeEnd:Color = Color(hexString: "#FF782C")
public static let OrangeStart:Color = Color(hexString: "#EC2301")
public static let LegendText:Color = Color(hexString: "#A7A6A8")
public static let LegendColor:Color = Color(hexString: "#E8E7EA")
public static let LegendDarkColor:Color = Color(hexString: "#545454")
@@ -30,62 +30,104 @@ public struct Colors {
public static let BorderBlue:Color = Color(hexString: "#4EBCFF")
}
public struct GradientColor {
public let start: Color
public let end: Color
init(start: Color, end: Color) {
self.start = start
self.end = end
}
public func getGradient() -> Gradient {
return Gradient(colors: [start, end])
}
}
public struct GradientColors {
public static let orange = GradientColor(start: Colors.OrangeStart, end: Colors.OrangeEnd)
public static let blue = GradientColor(start: Colors.GradientPurple, end: Colors.GradientNeonBlue)
public static let green = GradientColor(start: Color(hexString: "0BCDF7"), end: Color(hexString: "A2FEAE"))
public static let blu = GradientColor(start: Color(hexString: "0591FF"), end: Color(hexString: "29D9FE"))
public static let bluPurpl = GradientColor(start: Color(hexString: "4ABBFB"), end: Color(hexString: "8C00FF"))
public static let purple = GradientColor(start: Color(hexString: "741DF4"), end: Color(hexString: "C501B0"))
public static let prplPink = GradientColor(start: Color(hexString: "BC05AF"), end: Color(hexString: "FF1378"))
public static let prplNeon = GradientColor(start: Color(hexString: "FE019A"), end: Color(hexString: "FE0BF4"))
public static let orngPink = GradientColor(start: Color(hexString: "FF8E2D"), end: Color(hexString: "FF4E7A"))
}
public struct Styles {
public static let lineChartStyleOne = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
textColor: Color.black,
legendTextColor: Color.gray)
legendTextColor: Color.gray,
dropShadowColor: Color.gray)
public static let barChartStyleOrangeLight = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
textColor: Color.black,
legendTextColor: Color.gray)
legendTextColor: Color.gray,
dropShadowColor: Color.gray)
public static let barChartStyleOrangeDark = ChartStyle(
backgroundColor: Color.black,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
textColor: Color.white,
legendTextColor: Color.gray)
legendTextColor: Color.gray,
dropShadowColor: Color.gray)
public static let barChartStyleNeonBlueLight = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.GradientNeonBlue,
secondGradientColor: Colors.GradientPurple,
textColor: Color.black,
legendTextColor: Color.gray)
legendTextColor: Color.gray,
dropShadowColor: Color.gray)
public static let barChartStyleNeonBlueDark = ChartStyle(
backgroundColor: Color.black,
accentColor: Colors.GradientNeonBlue,
secondGradientColor: Colors.GradientPurple,
textColor: Color.white,
legendTextColor: Color.gray)
legendTextColor: Color.gray,
dropShadowColor: Color.gray)
public static let barChartMidnightGreenDark = ChartStyle(
backgroundColor: Color(hexString: "#36534D"), //3B5147, 313D34
accentColor: Color(hexString: "#FFD603"),
secondGradientColor: Color(hexString: "#FFCA04"),
textColor: Color.white,
legendTextColor: Color(hexString: "#D2E5E1"))
legendTextColor: Color(hexString: "#D2E5E1"),
dropShadowColor: Color.gray)
public static let barChartMidnightGreenLight = ChartStyle(
backgroundColor: Color.white,
accentColor: Color(hexString: "#84A094"), //84A094 , 698378
secondGradientColor: Color(hexString: "#50675D"),
textColor: Color.black,
legendTextColor:Color.gray)
legendTextColor:Color.gray,
dropShadowColor: Color.gray)
public static let pieChartStyleOne = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.OrangeEnd,
secondGradientColor: Colors.OrangeStart,
textColor: Color.black,
legendTextColor: Color.gray,
dropShadowColor: Color.gray)
public static let lineViewDarkMode = ChartStyle(
backgroundColor: Color.black,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
textColor: Color.black,
legendTextColor: Color.gray)
textColor: Color.white,
legendTextColor: Color.white,
dropShadowColor: Color.gray)
}
public struct ChartForm {
@@ -104,34 +146,48 @@ public struct ChartForm {
}
public struct ChartStyle {
public class ChartStyle {
public var backgroundColor: Color
public var accentColor: Color
public var secondGradientColor: Color
public var gradientColor: GradientColor
public var textColor: Color
public var legendTextColor: Color
public var dropShadowColor: Color
public weak var darkModeStyle: ChartStyle?
public init(backgroundColor: Color, accentColor: Color, secondGradientColor: Color, textColor: Color, legendTextColor: Color){
public init(backgroundColor: Color, accentColor: Color, secondGradientColor: Color, textColor: Color, legendTextColor: Color, dropShadowColor: Color){
self.backgroundColor = backgroundColor
self.accentColor = accentColor
self.secondGradientColor = secondGradientColor
self.gradientColor = GradientColor(start: accentColor, end: secondGradientColor)
self.textColor = textColor
self.legendTextColor = legendTextColor
self.dropShadowColor = dropShadowColor
}
public init(backgroundColor: Color, accentColor: Color, gradientColor: GradientColor, textColor: Color, legendTextColor: Color, dropShadowColor: Color){
self.backgroundColor = backgroundColor
self.accentColor = accentColor
self.gradientColor = gradientColor
self.textColor = textColor
self.legendTextColor = legendTextColor
self.dropShadowColor = dropShadowColor
}
public init(formSize: CGSize){
self.backgroundColor = Color.white
self.accentColor = Colors.OrangeStart
self.secondGradientColor = Colors.OrangeEnd
self.gradientColor = GradientColors.orange
self.legendTextColor = Color.gray
self.textColor = Color.black
self.dropShadowColor = Color.gray
}
}
public class ChartData: ObservableObject {
public class ChartData: ObservableObject, Identifiable {
@Published var points: [(String,Double)]
var valuesGiven: Bool = false
var ID = UUID()
public init<N: BinaryFloatingPoint>(points:[N]) {
self.points = points.map{("", Double($0))}
}
@@ -157,14 +213,32 @@ public class ChartData: ObservableObject {
}
}
public class MultiLineChartData: ChartData {
var gradient: GradientColor
public init<N: BinaryFloatingPoint>(points:[N], gradient: GradientColor) {
self.gradient = gradient
super.init(points: points)
}
public init<N: BinaryFloatingPoint>(points:[N], color: Color) {
self.gradient = GradientColor(start: color, end: color)
super.init(points: points)
}
public func getGradient() -> GradientColor {
return self.gradient
}
}
public class TestData{
static public var data:ChartData = ChartData(points: [37,72,51,22,39,47,66,85,50])
static public var values:ChartData = ChartData(values: [("2017 Q3",220),
("2017 Q4",1550),
("2018 Q1",8180),
("2018 Q2",18440),
("2018 Q3",55840),
("2018 Q4",63150), ("2019 Q1",50900), ("2019 Q2",77550), ("2019 Q3",79600), ("2019 Q4",92550)])
("2017 Q4",1550),
("2018 Q1",8180),
("2018 Q2",18440),
("2018 Q3",55840),
("2018 Q4",63150), ("2019 Q1",50900), ("2019 Q2",77550), ("2019 Q3",79600), ("2019 Q4",92550)])
}
+31 -85
View File
@@ -8,14 +8,19 @@
import SwiftUI
struct Line: View {
public struct Line: View {
@ObservedObject var data: ChartData
@Binding var frame: CGRect
@Binding var touchLocation: CGPoint
@Binding var showIndicator: Bool
@Binding var minDataValue: Double?
@Binding var maxDataValue: Double?
@State private var showFull: Bool = false
@State var showBackground: Bool = true
var gradient: GradientColor = GradientColor(start: Colors.GradientPurple, end: Colors.GradientNeonBlue)
var index:Int = 0
let padding:CGFloat = 30
var curvedLines: Bool = true
var stepWidth: CGFloat {
if data.points.count < 2 {
return 0
@@ -23,26 +28,38 @@ struct Line: View {
return frame.size.width / CGFloat(data.points.count-1)
}
var stepHeight: CGFloat {
var min: Double?
var max: Double?
let points = self.data.onlyPoints()
if let min = points.min(), let max = points.max(), min != max {
if minDataValue != nil && maxDataValue != nil {
min = minDataValue!
max = maxDataValue!
print(min,max)
}else if let minPoint = points.min(), let maxPoint = points.max(), minPoint != maxPoint {
min = minPoint
max = maxPoint
}else {
return 0
}
if let min = min, let max = max, min != max {
if (min <= 0){
return (frame.size.height-padding) / CGFloat(points.max()! - points.min()!)
return (frame.size.height-padding) / CGFloat(max - min)
}else{
return (frame.size.height-padding) / CGFloat(points.max()! + points.min()!)
return (frame.size.height-padding) / CGFloat(max + min)
}
}
return 0
}
var path: Path {
let points = self.data.onlyPoints()
return Path.quadCurvedPathWithPoints(points: points, step: CGPoint(x: stepWidth, y: stepHeight))
return curvedLines ? Path.quadCurvedPathWithPoints(points: points, step: CGPoint(x: stepWidth, y: stepHeight), globalOffset: minDataValue) : Path.linePathWithPoints(points: points, step: CGPoint(x: stepWidth, y: stepHeight))
}
var closedPath: Path {
let points = self.data.onlyPoints()
return Path.quadClosedCurvedPathWithPoints(points: points, step: CGPoint(x: stepWidth, y: stepHeight))
return curvedLines ? Path.quadClosedCurvedPathWithPoints(points: points, step: CGPoint(x: stepWidth, y: stepHeight), globalOffset: minDataValue) : Path.closedLinePathWithPoints(points: points, step: CGPoint(x: stepWidth, y: stepHeight))
}
var body: some View {
public var body: some View {
ZStack {
if(self.showFull && self.showBackground){
self.closedPath
@@ -54,16 +71,16 @@ struct Line: View {
}
self.path
.trim(from: 0, to: self.showFull ? 1:0)
.stroke(LinearGradient(gradient: Gradient(colors: [Colors.GradientPurple, Colors.GradientNeonBlue]), startPoint: .leading, endPoint: .trailing) ,style: StrokeStyle(lineWidth: 3))
.stroke(LinearGradient(gradient: gradient.getGradient(), startPoint: .leading, endPoint: .trailing) ,style: StrokeStyle(lineWidth: 3, lineJoin: .round))
.rotationEffect(.degrees(180), anchor: .center)
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
.animation(.easeOut(duration: 1.2))
.animation(Animation.easeOut(duration: 1.2).delay(Double(self.index)*0.4))
.onAppear {
self.showFull = true
}
.onDisappear {
self.showFull = false
}
}
.onDisappear {
self.showFull = false
}
.drawingGroup()
if(self.showIndicator) {
IndicatorPoint()
@@ -81,81 +98,10 @@ struct Line: View {
}
extension CGPoint {
static func getMidPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
return CGPoint(
x: point1.x + (point2.x - point1.x) / 2,
y: point1.y + (point2.y - point1.y) / 2
)
}
func dist(to: CGPoint) -> CGFloat {
return sqrt((pow(self.x - to.x, 2) + pow(self.y - to.y, 2)))
}
static func midPointForPoints(p1:CGPoint, p2:CGPoint) -> CGPoint {
return CGPoint(x:(p1.x + p2.x) / 2,y: (p1.y + p2.y) / 2)
}
static func controlPointForPoints(p1:CGPoint, p2:CGPoint) -> CGPoint {
var controlPoint = CGPoint.midPointForPoints(p1:p1, p2:p2)
let diffY = abs(p2.y - controlPoint.y)
if (p1.y < p2.y){
controlPoint.y += diffY
} else if (p1.y > p2.y) {
controlPoint.y -= diffY
}
return controlPoint
}
}
extension Path {
static func quadCurvedPathWithPoints(points:[Double], step:CGPoint) -> Path {
var path = Path()
if (points.count < 2){
return path
}
guard var offset = points.min() else { return path }
offset -= 3
var p1 = CGPoint(x: 0, y: CGFloat(points[0]-offset)*step.y)
path.move(to: p1)
for pointIndex in 1..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]-offset))
let midPoint = CGPoint.midPointForPoints(p1: p1, p2: p2)
path.addQuadCurve(to: midPoint, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p1))
path.addQuadCurve(to: p2, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p2))
p1 = p2
}
return path
}
static func quadClosedCurvedPathWithPoints(points:[Double], step:CGPoint) -> Path {
var path = Path()
if (points.count < 2){
return path
}
guard var offset = points.min() else { return path }
offset -= 3
path.move(to: .zero)
var p1 = CGPoint(x: 0, y: CGFloat(points[0]-offset)*step.y)
path.addLine(to: p1)
for pointIndex in 1..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]-offset))
let midPoint = CGPoint.midPointForPoints(p1: p1, p2: p2)
path.addQuadCurve(to: midPoint, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p1))
path.addQuadCurve(to: p2, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p2))
p1 = p2
}
path.addLine(to: CGPoint(x: p1.x, y: 0))
path.closeSubpath()
return path
}
}
struct Line_Previews: PreviewProvider {
static var previews: some View {
GeometryReader{ geometry in
Line(data: ChartData(points: [12,-230,10,54]), frame: .constant(geometry.frame(in: .local)), touchLocation: .constant(CGPoint(x: 100, y: 12)), showIndicator: .constant(true))
Line(data: ChartData(points: [12,-230,10,54]), frame: .constant(geometry.frame(in: .local)), touchLocation: .constant(CGPoint(x: 100, y: 12)), showIndicator: .constant(true), minDataValue: .constant(nil), maxDataValue: .constant(nil))
}.frame(width: 320, height: 160)
}
}
@@ -9,21 +9,22 @@
import SwiftUI
public struct LineChartView: View {
// let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
@Environment(\.colorScheme) var colorScheme: ColorScheme
@ObservedObject var data:ChartData
public var title: String
public var legend: String?
public var style: ChartStyle
public var darkModeStyle: ChartStyle
public var formSize:CGSize
public var dropShadow: Bool
public var valueSpecifier:String
@State private var touchLocation:CGPoint = .zero
@State private var showIndicatorDot: Bool = false
@State private var currentValue: Double = 2 {
didSet{
if (oldValue != self.currentValue && showIndicatorDot) {
// selectionFeedbackGenerator.selectionChanged()
HapticFeedback.playSelection()
}
@@ -32,11 +33,20 @@ public struct LineChartView: View {
let frame = CGSize(width: 180, height: 120)
private var rateValue: Int
public init(data: [Double], title: String, legend: String? = nil, style: ChartStyle = Styles.lineChartStyleOne, form: CGSize? = ChartForm.medium ,rateValue: Int? = 14, dropShadow: Bool? = true, valueSpecifier: String? = "%.1f"){
public init(data: [Double],
title: String,
legend: String? = nil,
style: ChartStyle = Styles.lineChartStyleOne,
form: CGSize? = ChartForm.medium,
rateValue: Int? = 14,
dropShadow: Bool? = true,
valueSpecifier: String? = "%.1f") {
self.data = ChartData(points: data)
self.title = title
self.legend = legend
self.style = style
self.darkModeStyle = style.darkModeStyle != nil ? style.darkModeStyle! : Styles.lineViewDarkMode
self.formSize = form!
self.rateValue = rateValue!
self.dropShadow = dropShadow!
@@ -45,13 +55,21 @@ public struct LineChartView: View {
public var body: some View {
ZStack(alignment: .center){
RoundedRectangle(cornerRadius: 20).fill(self.style.backgroundColor).frame(width: frame.width, height: 240, alignment: .center).shadow(radius: self.dropShadow ? 8 : 0)
RoundedRectangle(cornerRadius: 20)
.fill(self.colorScheme == .dark ? self.darkModeStyle.backgroundColor : self.style.backgroundColor)
.frame(width: frame.width, height: 240, alignment: .center)
.shadow(color: self.style.dropShadowColor, radius: self.dropShadow ? 8 : 0)
VStack(alignment: .leading){
if(!self.showIndicatorDot){
VStack(alignment: .leading, spacing: 8){
Text(self.title).font(.title).bold().foregroundColor(self.style.textColor)
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.style.legendTextColor)
Text(self.legend!)
.font(.callout)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor :self.style.legendTextColor)
}
HStack {
if (self.rateValue >= 0){
@@ -74,12 +92,16 @@ public struct LineChartView: View {
Spacer()
}
.transition(.scale)
// .animation(.spring())
}
Spacer()
GeometryReader{ geometry in
Line(data: self.data, frame: .constant(geometry.frame(in: .local)), touchLocation: self.$touchLocation, showIndicator: self.$showIndicatorDot)
Line(data: self.data,
frame: .constant(geometry.frame(in: .local)),
touchLocation: self.$touchLocation,
showIndicator: self.$showIndicatorDot,
minDataValue: .constant(nil),
maxDataValue: .constant(nil)
)
}
.frame(width: frame.width, height: frame.height + 30)
.clipShape(RoundedRectangle(cornerRadius: 20))
+47 -57
View File
@@ -13,8 +13,9 @@ public struct LineView: View {
public var title: String?
public var legend: String?
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
@@ -24,51 +25,76 @@ public struct LineView: View {
@State private var currentDataNumber: Double = 0
@State private var hideHorizontalLines: Bool = false
public init(data: [Double], title: String? = nil, legend: String? = nil, style: ChartStyle? = Styles.lineChartStyleOne, valueSpecifier: String? = "%.1f"){
public init(data: [Double],
title: String? = nil,
legend: String? = nil,
style: ChartStyle = Styles.lineChartStyleOne,
valueSpecifier: String? = "%.1f") {
self.data = ChartData(points: data)
self.title = title
self.legend = legend
self.style = style!
self.style = style
self.valueSpecifier = valueSpecifier!
self.darkModeStyle = style.darkModeStyle != nil ? style.darkModeStyle! : Styles.lineViewDarkMode
}
public var body: some View {
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 ? Color.white : self.style.textColor)
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 ? Color.white : self.style.legendTextColor)
Text(self.legend!)
.font(.callout)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
}
}.offset(x: 0, y: 20)
ZStack{
GeometryReader{ reader in
Rectangle().foregroundColor(self.colorScheme == .dark ? Color.black : self.style.backgroundColor)
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)
Legend(data: self.data,
frame: .constant(reader.frame(in: .local)), hideHorizontalLines: self.$hideHorizontalLines)
.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 - 30, height: reader.frame(in: .local).height)), touchLocation: self.$indicatorLocation,showIndicator: self.$hideHorizontalLines ,showBackground: false).offset(x: 30, y: 0)
.onAppear(){
self.showLegend.toggle()
}
}.frame(width: geometry.frame(in: .local).size.width, height: 240).offset(x: 0, y: 40 )
Line(data: self.data,
frame: .constant(CGRect(x: 0, y: 0, width: reader.frame(in: .local).width - 30, height: reader.frame(in: .local).height)),
touchLocation: self.$indicatorLocation,
showIndicator: self.$hideHorizontalLines,
minDataValue: .constant(nil),
maxDataValue: .constant(nil),
showBackground: false
)
.offset(x: 30, y: 0)
.onAppear(){
self.showLegend = true
}
.onDisappear(){
self.showLegend = false
}
}
.frame(width: geometry.frame(in: .local).size.width, height: 240)
.offset(x: 0, y: 40 )
MagnifierRect(currentNumber: self.$currentDataNumber, valueSpecifier: self.valueSpecifier)
.opacity(self.opacity)
.offset(x: self.dragLocation.x - geometry.frame(in: .local).size.width/2, y: 36)
}
.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
})
.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({ value in
self.opacity = 0
self.hideHorizontalLines = false
@@ -82,7 +108,7 @@ public struct LineView: View {
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 (index >= 0 && index < points.count){
self.currentDataNumber = points[index]
@@ -98,39 +124,3 @@ struct LineView_Previews: PreviewProvider {
}
}
struct IndicatorCircle: View {
var body: some View {
Circle()
.size(width: 12, height: 12)
.fill(Colors.BorderBlue)
}
}
struct MagnifierRect: View {
@Binding var currentNumber: Double
var valueSpecifier:String
@Environment(\.colorScheme) var colorScheme: ColorScheme
var body: some View {
ZStack{
Text("\(self.currentNumber, specifier: valueSpecifier)")
.font(.system(size: 18, weight: .bold))
.offset(x: 0, y:-110)
// .animation(.spring())
.foregroundColor(self.colorScheme == .dark ? Color.white : Color.black)
if (self.colorScheme == .dark ){
RoundedRectangle(cornerRadius: 16)
.stroke(Color.white, lineWidth: self.colorScheme == .dark ? 2 : 0)
.frame(width: 60, height: 260)
}else{
RoundedRectangle(cornerRadius: 16)
.frame(width: 60, height: 280)
.foregroundColor(Color.white)
.shadow(color: Colors.LegendText, radius: 12, x: 0, y: 6 )
.blendMode(.multiply)
}
}//.animation(.linear)
}
}
@@ -0,0 +1,33 @@
//
// MagnifierRect.swift
//
//
// Created by Samu András on 2020. 03. 04..
//
import SwiftUI
public struct MagnifierRect: View {
@Binding var currentNumber: Double
var valueSpecifier:String
@Environment(\.colorScheme) var colorScheme: ColorScheme
public var body: some View {
ZStack{
Text("\(self.currentNumber, specifier: valueSpecifier)")
.font(.system(size: 18, weight: .bold))
.offset(x: 0, y:-110)
.foregroundColor(self.colorScheme == .dark ? Color.white : Color.black)
if (self.colorScheme == .dark ){
RoundedRectangle(cornerRadius: 16)
.stroke(Color.white, lineWidth: self.colorScheme == .dark ? 2 : 0)
.frame(width: 60, height: 260)
}else{
RoundedRectangle(cornerRadius: 16)
.frame(width: 60, height: 280)
.foregroundColor(Color.white)
.shadow(color: Colors.LegendText, radius: 12, x: 0, y: 6 )
.blendMode(.multiply)
}
}
}
}
@@ -0,0 +1,163 @@
//
// File.swift
//
//
// Created by Samu András on 2020. 02. 19..
//
import SwiftUI
public struct MultiLineChartView: View {
@Environment(\.colorScheme) var colorScheme: ColorScheme
var data:[MultiLineChartData]
public var title: String
public var legend: String?
public var style: ChartStyle
public var darkModeStyle: ChartStyle
public var formSize:CGSize
public var dropShadow: Bool
public var valueSpecifier:String
@State private var touchLocation:CGPoint = .zero
@State private var showIndicatorDot: Bool = false
@State private var currentValue: Double = 2 {
didSet{
if (oldValue != self.currentValue && showIndicatorDot) {
HapticFeedback.playSelection()
}
}
}
var globalMin:Double {
if let min = data.flatMap({$0.onlyPoints()}).min() {
return min
}
return 0
}
var globalMax:Double {
if let max = data.flatMap({$0.onlyPoints()}).max() {
return max
}
return 0
}
let frame = CGSize(width: 180, height: 120)
private var rateValue: Int
public init(data: [([Double], GradientColor)],
title: String,
legend: String? = nil,
style: ChartStyle = Styles.lineChartStyleOne,
form: CGSize? = ChartForm.medium,
rateValue: Int? = 14,
dropShadow: Bool? = true,
valueSpecifier: String? = "%.1f") {
self.data = data.map({ MultiLineChartData(points: $0.0, gradient: $0.1)})
self.title = title
self.legend = legend
self.style = style
self.darkModeStyle = style.darkModeStyle != nil ? style.darkModeStyle! : Styles.lineViewDarkMode
self.formSize = form!
self.rateValue = rateValue!
self.dropShadow = dropShadow!
self.valueSpecifier = valueSpecifier!
}
public var body: some View {
ZStack(alignment: .center){
RoundedRectangle(cornerRadius: 20)
.fill(self.colorScheme == .dark ? self.darkModeStyle.backgroundColor : self.style.backgroundColor)
.frame(width: frame.width, height: 240, alignment: .center)
.shadow(radius: self.dropShadow ? 8 : 0)
VStack(alignment: .leading){
if(!self.showIndicatorDot){
VStack(alignment: .leading, spacing: 8){
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)
}
HStack {
if (self.rateValue >= 0){
Image(systemName: "arrow.up")
}else{
Image(systemName: "arrow.down")
}
Text("\(self.rateValue)%")
}
}
.transition(.opacity)
.animation(.easeIn(duration: 0.1))
.padding([.leading, .top])
}else{
HStack{
Spacer()
Text("\(self.currentValue, specifier: self.valueSpecifier)")
.font(.system(size: 41, weight: .bold, design: .default))
.offset(x: 0, y: 30)
Spacer()
}
.transition(.scale)
}
Spacer()
GeometryReader{ geometry in
ZStack{
ForEach(0..<self.data.count) { i in
Line(data: self.data[i],
frame: .constant(geometry.frame(in: .local)),
touchLocation: self.$touchLocation,
showIndicator: self.$showIndicatorDot,
minDataValue: .constant(self.globalMin),
maxDataValue: .constant(self.globalMax),
showBackground: false,
gradient: self.data[i].getGradient(),
index: i)
}
}
}
.frame(width: frame.width, height: frame.height + 30)
.clipShape(RoundedRectangle(cornerRadius: 20))
.offset(x: 0, y: 0)
}.frame(width: self.formSize.width, height: self.formSize.height)
}
.gesture(DragGesture()
.onChanged({ value in
// self.touchLocation = value.location
// self.showIndicatorDot = true
// self.getClosestDataPoint(toPoint: value.location, width:self.frame.width, height: self.frame.height)
})
.onEnded({ value in
self.showIndicatorDot = false
})
)
}
// @discardableResult 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(round((toPoint.x)/stepWidth))
// if (index >= 0 && index < points.count){
// self.currentValue = points[index]
// return CGPoint(x: CGFloat(index)*stepWidth, y: CGFloat(points[index])*stepHeight)
// }
// return .zero
// }
}
struct MultiWidgetView_Previews: PreviewProvider {
static var previews: some View {
Group {
MultiLineChartView(data: [([8,23,54,32,12,37,7,23,43], GradientColors.orange)], title: "Line chart", legend: "Basic")
.environment(\.colorScheme, .light)
}
}
}
@@ -12,31 +12,31 @@ extension Path {
// percent difference between points
let boundsDistance: CGFloat = 0.001
let completion: CGFloat = 1 - boundsDistance
let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent)
let start = pct > completion ? completion : pct - boundsDistance
let end = pct > completion ? 1 : pct + boundsDistance
return trimmedPath(from: start, to: end)
}
func point(for percent: CGFloat) -> CGPoint {
let path = trimmedPath(for: percent)
return CGPoint(x: path.boundingRect.midX, y: path.boundingRect.midY)
}
func point(to maxX: CGFloat) -> CGPoint {
let total = length
let sub = length(to: maxX)
let percent = sub / total
return point(for: percent)
}
var length: CGFloat {
var ret: CGFloat = 0.0
var start: CGPoint?
var point = CGPoint.zero
forEach { ele in
switch ele {
case .move(let to):
@@ -63,13 +63,13 @@ extension Path {
}
return ret
}
func length(to maxX: CGFloat) -> CGFloat {
var ret: CGFloat = 0.0
var start: CGPoint?
var point = CGPoint.zero
var finished = false
forEach { ele in
if finished {
return
@@ -114,6 +114,81 @@ extension Path {
}
return ret
}
static func quadCurvedPathWithPoints(points:[Double], step:CGPoint, globalOffset: Double? = nil) -> Path {
var path = Path()
if (points.count < 2){
return path
}
let offset = globalOffset ?? points.min()!
// guard let offset = points.min() else { return path }
var p1 = CGPoint(x: 0, y: CGFloat(points[0]-offset)*step.y)
path.move(to: p1)
for pointIndex in 1..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]-offset))
let midPoint = CGPoint.midPointForPoints(p1: p1, p2: p2)
path.addQuadCurve(to: midPoint, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p1))
path.addQuadCurve(to: p2, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p2))
p1 = p2
}
return path
}
static func quadClosedCurvedPathWithPoints(points:[Double], step:CGPoint, globalOffset: Double? = nil) -> Path {
var path = Path()
if (points.count < 2){
return path
}
let offset = globalOffset ?? points.min()!
// guard let offset = points.min() else { return path }
path.move(to: .zero)
var p1 = CGPoint(x: 0, y: CGFloat(points[0]-offset)*step.y)
path.addLine(to: p1)
for pointIndex in 1..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]-offset))
let midPoint = CGPoint.midPointForPoints(p1: p1, p2: p2)
path.addQuadCurve(to: midPoint, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p1))
path.addQuadCurve(to: p2, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p2))
p1 = p2
}
path.addLine(to: CGPoint(x: p1.x, y: 0))
path.closeSubpath()
return path
}
static func linePathWithPoints(points:[Double], step:CGPoint) -> Path {
var path = Path()
if (points.count < 2){
return path
}
guard let offset = points.min() else { return path }
let p1 = CGPoint(x: 0, y: CGFloat(points[0]-offset)*step.y)
path.move(to: p1)
for pointIndex in 1..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]-offset))
path.addLine(to: p2)
}
return path
}
static func closedLinePathWithPoints(points:[Double], step:CGPoint) -> Path {
var path = Path()
if (points.count < 2){
return path
}
guard let offset = points.min() else { return path }
var p1 = CGPoint(x: 0, y: CGFloat(points[0]-offset)*step.y)
path.move(to: p1)
for pointIndex in 1..<points.count {
p1 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]-offset))
path.addLine(to: p1)
}
path.addLine(to: CGPoint(x: p1.x, y: 0))
path.closeSubpath()
return path
}
}
extension CGPoint {
@@ -122,40 +197,40 @@ extension CGPoint {
let y = self.y + (x - self.x) * a
return CGPoint(x: x, y: y)
}
func line(to: CGPoint) -> CGFloat {
dist(to: to)
}
func line(to: CGPoint, x: CGFloat) -> CGFloat {
dist(to: point(to: to, x: x))
}
func quadCurve(to: CGPoint, control: CGPoint) -> CGFloat {
var dist: CGFloat = 0
let steps: CGFloat = 100
for i in 0..<Int(steps) {
let t0 = CGFloat(i) / steps
let t1 = CGFloat(i+1) / steps
let a = point(to: to, t: t0, control: control)
let b = point(to: to, t: t1, control: control)
dist += a.line(to: b)
}
return dist
}
func quadCurve(to: CGPoint, control: CGPoint, x: CGFloat) -> CGFloat {
var dist: CGFloat = 0
let steps: CGFloat = 100
for i in 0..<Int(steps) {
let t0 = CGFloat(i) / steps
let t1 = CGFloat(i+1) / steps
let a = point(to: to, t: t0, control: control)
let b = point(to: to, t: t1, control: control)
if a.x >= x {
return dist
} else if b.x > x {
@@ -165,47 +240,47 @@ extension CGPoint {
dist += a.line(to: b)
return dist
}
dist += a.line(to: b)
}
return dist
}
func point(to: CGPoint, t: CGFloat, control: CGPoint) -> CGPoint {
let x = CGPoint.value(x: self.x, y: to.x, t: t, c: control.x)
let y = CGPoint.value(x: self.y, y: to.y, t: t, c: control.y)
return CGPoint(x: x, y: y)
}
func curve(to: CGPoint, control1: CGPoint, control2: CGPoint) -> CGFloat {
var dist: CGFloat = 0
let steps: CGFloat = 100
for i in 0..<Int(steps) {
let t0 = CGFloat(i) / steps
let t1 = CGFloat(i+1) / steps
let a = point(to: to, t: t0, control1: control1, control2: control2)
let b = point(to: to, t: t1, control1: control1, control2: control2)
dist += a.line(to: b)
}
return dist
}
func curve(to: CGPoint, control1: CGPoint, control2: CGPoint, x: CGFloat) -> CGFloat {
var dist: CGFloat = 0
let steps: CGFloat = 100
for i in 0..<Int(steps) {
let t0 = CGFloat(i) / steps
let t1 = CGFloat(i+1) / steps
let a = point(to: to, t: t0, control1: control1, control2: control2)
let b = point(to: to, t: t1, control1: control1, control2: control2)
if a.x >= x {
return dist
} else if b.x > x {
@@ -215,20 +290,20 @@ extension CGPoint {
dist += a.line(to: b)
return dist
}
dist += a.line(to: b)
}
return dist
}
func point(to: CGPoint, t: CGFloat, control1: CGPoint, control2: CGPoint) -> CGPoint {
let x = CGPoint.value(x: self.x, y: to.x, t: t, c1: control1.x, c2: control2.x)
let y = CGPoint.value(x: self.y, y: to.y, t: t, c1: control1.y, c2: control2.x)
return CGPoint(x: x, y: y)
}
static func value(x: CGFloat, y: CGFloat, t: CGFloat, c: CGFloat) -> CGFloat {
var value: CGFloat = 0.0
// (1-t)^2 * p0 + 2 * (1-t) * t * c1 + t^2 * p1
@@ -237,7 +312,7 @@ extension CGPoint {
value += pow(t, 2) * y
return value
}
static func value(x: CGFloat, y: CGFloat, t: CGFloat, c1: CGFloat, c2: CGFloat) -> CGFloat {
var value: CGFloat = 0.0
// (1-t)^3 * p0 + 3 * (1-t)^2 * t * c1 + 3 * (1-t) * t^2 * c2 + t^3 * p1
@@ -247,5 +322,32 @@ extension CGPoint {
value += pow(t, 3) * y
return value
}
static func getMidPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
return CGPoint(
x: point1.x + (point2.x - point1.x) / 2,
y: point1.y + (point2.y - point1.y) / 2
)
}
func dist(to: CGPoint) -> CGFloat {
return sqrt((pow(self.x - to.x, 2) + pow(self.y - to.y, 2)))
}
static func midPointForPoints(p1:CGPoint, p2:CGPoint) -> CGPoint {
return CGPoint(x:(p1.x + p2.x) / 2,y: (p1.y + p2.y) / 2)
}
static func controlPointForPoints(p1:CGPoint, p2:CGPoint) -> CGPoint {
var controlPoint = CGPoint.midPointForPoints(p1:p1, p2:p2)
let diffY = abs(p2.y - controlPoint.y)
if (p1.y < p2.y){
controlPoint.y += diffY
} else if (p1.y > p2.y) {
controlPoint.y -= diffY
}
return controlPoint
}
}
@@ -16,7 +16,7 @@ public struct PieChartView : View {
public var formSize:CGSize
public var dropShadow: Bool
public init(data: [Double], title: String, legend: String? = nil, style: ChartStyle = Styles.pieChartStyleOne, form: CGSize? = ChartForm.medium, dropShadow: Bool? = true ){
public init(data: [Double], title: String, legend: String? = nil, style: ChartStyle = Styles.pieChartStyleOne, form: CGSize? = ChartForm.medium, dropShadow: Bool? = true){
self.data = data
self.title = title
self.legend = legend
@@ -30,7 +30,7 @@ public struct PieChartView : View {
Rectangle()
.fill(self.style.backgroundColor)
.cornerRadius(20)
.shadow(color: Color.gray, radius: self.dropShadow ? 12 : 0)
.shadow(color: self.style.dropShadowColor, radius: self.dropShadow ? 12 : 0)
VStack(alignment: .leading){
HStack{
Text(self.title)