diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/andrassamu.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/andrassamu.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..9eec9760 Binary files /dev/null and b/.swiftpm/xcode/package.xcworkspace/xcuserdata/andrassamu.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/README.md b/README.md index 091e63a4..e301d607 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ Swift package for displaying charts effortlessly. -![SwiftUI Charts](./chartview.gif "SwiftUI Charts") +![SwiftUI Charts](./showcase1.gif "SwiftUI Charts") -It supports currently: -* barcharts -* piecharts +It supports: +* Line charts +* Bar charts +* Pie charts ### Installation: @@ -20,40 +21,63 @@ import the package in the file you would like to use it: `import SwiftUICharts` You can display a Chart by adding a chart view to your parent view: -Barchart: +## Line charts +![Line Charts](./showcase3.gif "Line Charts") + +**Line chart is interactive, so you can drag across to reveal the data points** + +You can add a line chart with the following code: + ```swift -ChartView(data: [8,23,54,32,12,37,7,23,43], title: "Barchart") + LineChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", legend: "Legendary") // legend is optional ``` -Piechart: + +## Bar charts +![Bar Charts](./showcase2.gif "Bar Charts") + +**Bar chart is interactive, so you can drag across to reveal the data points** + +You can add a bar chart with the following code: + ```swift -PieChartView(data:[43,56,78,34], title: "Piechart") + BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", legend: "Legendary") // legend is optional ``` -You can optionally configure: -* legend +You can add different formats: +* Small `Form.small` +* Medium `Form.medium` +* Large `Form.large` + + ```swift + BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", style: ChartStyle(formSize: Form.small)) + ``` + + ### You can customize styling of the chart with a ChartStyle object: + +Customizable: * background color * accent color -* size format +* second gradient color +* chart form size +* text color +* legend text color -### Size format (only for bar charts yet!) +```swift + let chartStyle = ChartStyle(backgroundColor: Color.black, accentColor: Colors.OrangeStart, secondGradientColor: Colors.OrangeEnd, chartFormSize: Form.medium, textColor: Color.white, legendTextColor: Color.white ) + ... + BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", style: chartStyle) +``` -![Chart forms](./chartforms.png "Chart forms") +![Custom Charts](./showcase5.png "Custom Charts") -Can be -* small -* medium -* large -```swift -ChartView(data: [12,17,24,33,23,56], title: "Chart two", form: Form.small) -``` +## Pie charts +![Pie Charts](./showcase4.png "Pie Charts") -### Customizing color: -I added color constants, so you can predefine your color palette. To do so, you can find `Colors` struct in the ChartColors swift file. +You can add a line chart with the following code: ```swift -ChartView(data: [12,17,24,33,23,56], title: "Chart two", backgroundColor:Colors.color3 , accentColor:Colors.color3Accent) + PieChartView(data: [8,23,54,32], title: "Title", legend: "Legendary") // legend is optional ``` - diff --git a/Sources/SwiftUICharts/ChartCell.swift b/Sources/SwiftUICharts/BarChart/BarChartCell.swift similarity index 50% rename from Sources/SwiftUICharts/ChartCell.swift rename to Sources/SwiftUICharts/BarChart/BarChartCell.swift index f493f10f..1414ddb8 100644 --- a/Sources/SwiftUICharts/ChartCell.swift +++ b/Sources/SwiftUICharts/BarChart/BarChartCell.swift @@ -8,7 +8,7 @@ import SwiftUI -public struct ChartCell : View { +public struct BarChartCell : View { var value: Double var index: Int = 0 var width: Float @@ -16,25 +16,34 @@ public struct ChartCell : View { var cellWidth: Double { 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] + } @State var scaleValue: Double = 0 + @Binding var touchLocation: CGFloat public var body: some View { ZStack { - Rectangle() - .cornerRadius(4) + RoundedRectangle(cornerRadius: 4) + .fill(LinearGradient(gradient: Gradient(colors: gradientColors), startPoint: .bottom, endPoint: .top)) } .frame(width: CGFloat(self.cellWidth)) .scaleEffect(CGSize(width: 1, height: self.scaleValue), anchor: .bottom) .onAppear(){ self.scaleValue = self.value } - .animation(Animation.spring().delay(Double(self.index) * 0.04)) + .animation(Animation.spring().delay(self.touchLocation < 0 ? Double(self.index) * 0.04 : 0)) } } #if DEBUG struct ChartCell_Previews : PreviewProvider { static var previews: some View { - ChartCell(value: Double(0.75), width: 320, numberOfDataPoints: 12) + BarChartCell(value: Double(0.75), width: 320, numberOfDataPoints: 12, accentColor: Colors.OrangeStart, secondGradientAccentColor: nil, touchLocation: .constant(-1)) } } #endif diff --git a/Sources/SwiftUICharts/BarChart/BarChartRow.swift b/Sources/SwiftUICharts/BarChart/BarChartRow.swift new file mode 100644 index 00000000..ec17ef0a --- /dev/null +++ b/Sources/SwiftUICharts/BarChart/BarChartRow.swift @@ -0,0 +1,39 @@ +// +// ChartRow.swift +// ChartView +// +// Created by András Samu on 2019. 06. 12.. +// Copyright © 2019. András Samu. All rights reserved. +// + +import SwiftUI + +public struct BarChartRow : View { + var data: [Int] + var accentColor: Color + var secondGradientAccentColor: Color? + var maxValue: Int { + data.max() ?? 0 + } + @Binding var touchLocation: CGFloat + 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.. 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) + + } + } + .padding([.top, .leading, .trailing], 10) + } + } +} + +#if DEBUG +struct ChartRow_Previews : PreviewProvider { + static var previews: some View { + BarChartRow(data: [8,23,54,32,12,37,7], accentColor: Colors.OrangeStart, touchLocation: .constant(-1)) + } +} +#endif diff --git a/Sources/SwiftUICharts/BarChart/BarChartView.swift b/Sources/SwiftUICharts/BarChart/BarChartView.swift new file mode 100644 index 00000000..3de71d97 --- /dev/null +++ b/Sources/SwiftUICharts/BarChart/BarChartView.swift @@ -0,0 +1,104 @@ +// +// ChartView.swift +// ChartView +// +// Created by András Samu on 2019. 06. 12.. +// Copyright © 2019. András Samu. All rights reserved. +// + +import SwiftUI + +public struct BarChartView : View { + public var data: [Int] + public var title: String + public var legend: String? + public var style: ChartStyle + let selectionFeedbackGenerator = UISelectionFeedbackGenerator() + + @State private var touchLocation: CGFloat = -1.0 + @State private var showValue: Bool = false + @State private var currentValue: Int = 0 { + didSet{ + if(oldValue != self.currentValue && self.showValue) { + selectionFeedbackGenerator.selectionChanged() + } + } + } + var isFullWidth:Bool { + return self.style.chartFormSize == Form.large + } + public init(data: [Int], title: String, legend: String? = nil, style: ChartStyle = Styles.barChartStyleOne ){ + self.data = data + self.title = title + self.legend = legend + self.style = style + } + + public var body: some View { + ZStack{ + Rectangle() + .fill(self.style.backgroundColor) + .cornerRadius(20) + .shadow(color: Color.gray, radius: 8 ) + VStack(alignment: .leading){ + HStack{ + if(!showValue){ + Text(self.title) + .font(.headline) + .foregroundColor(self.style.textColor) + }else{ + Text("\(self.currentValue)") + .font(.headline) + .foregroundColor(self.style.textColor) + } + if(self.style.chartFormSize == Form.large && self.legend != nil && !showValue) { + Text(self.legend!) + .font(.callout) + .foregroundColor(self.style.accentColor) + .transition(.opacity) + .animation(.easeOut) + } + Spacer() + Image(systemName: "waveform.path.ecg") + .imageScale(.large) + .foregroundColor(self.style.legendTextColor) + }.padding() + BarChartRow(data: data, accentColor: self.style.accentColor, secondGradientAccentColor: self.style.secondGradientColor, touchLocation: self.$touchLocation) + if self.legend != nil && self.style.chartFormSize == Form.medium { + Text(self.legend!) + .font(.headline) + .foregroundColor(self.style.legendTextColor) + .padding() + } + + } + }.frame(minWidth:self.style.chartFormSize.width, maxWidth: self.isFullWidth ? .infinity : self.style.chartFormSize.width, minHeight:self.style.chartFormSize.height, maxHeight:self.style.chartFormSize.height) + .gesture(DragGesture() + .onChanged({ value in + self.touchLocation = value.location.x/self.style.chartFormSize.width + self.showValue = true + self.currentValue = self.getCurrentValue() + }) + .onEnded({ value in + self.showValue = false + self.touchLocation = -1 + }) + ) + .gesture(TapGesture() + ) + } + + func getCurrentValue()-> Int{ + let index = max(0,min(self.data.count-1,Int(floor((self.touchLocation*self.style.chartFormSize.width)/(self.style.chartFormSize.width/CGFloat(self.data.count)))))) + print(index) + return self.data[index] + } +} + +#if DEBUG +struct ChartView_Previews : PreviewProvider { + static var previews: some View { + BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", legend: "Legendary") + } +} +#endif diff --git a/Sources/SwiftUICharts/ChartColors.swift b/Sources/SwiftUICharts/ChartColors.swift deleted file mode 100644 index 0c695a75..00000000 --- a/Sources/SwiftUICharts/ChartColors.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// File.swift -// -// -// Created by András Samu on 2019. 07. 19.. -// - -import Foundation -import SwiftUI -public struct Colors { - public static let color1:Color = Color(hexString: "#E2FAE7") - public static let color1Accent:Color = Color(hexString: "#72BF82") - public static let color2:Color = Color(hexString: "#EEF1FF") - public static let color2Accent:Color = Color(hexString: "#4266E8") - public static let color3:Color = Color(hexString: "#FCECEA") - public static let color3Accent:Color = Color(hexString: "#E1614C") -} - - -extension Color { - init(hexString: String) { - let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) - var int = UInt32() - Scanner(string: hex).scanHexInt32(&int) - let r, g, b: UInt32 - switch hex.count { - case 3: // RGB (12-bit) - (r, g, b) = ((int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) - case 6: // RGB (24-bit) - (r, g, b) = (int >> 16, int >> 8 & 0xFF, int & 0xFF) - case 8: // ARGB (32-bit) - (r, g, b) = (int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) - default: - (r, g, b) = (0, 0, 0) - } - self.init(red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255) - } -} diff --git a/Sources/SwiftUICharts/ChartRow.swift b/Sources/SwiftUICharts/ChartRow.swift deleted file mode 100644 index 7bc50d7d..00000000 --- a/Sources/SwiftUICharts/ChartRow.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// ChartRow.swift -// ChartView -// -// Created by András Samu on 2019. 06. 12.. -// Copyright © 2019. András Samu. All rights reserved. -// - -import SwiftUI - -public struct ChartRow : View { - var data: [Int] - var maxValue: Int { - data.max() ?? 0 - } - 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..> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (r, g, b) = (int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (r, g, b) = (int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (r, g, b) = (0, 0, 0) + } + self.init(red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255) + } +} diff --git a/Sources/SwiftUICharts/LineChart/IndicatorPoint.swift b/Sources/SwiftUICharts/LineChart/IndicatorPoint.swift new file mode 100644 index 00000000..2e8667da --- /dev/null +++ b/Sources/SwiftUICharts/LineChart/IndicatorPoint.swift @@ -0,0 +1,28 @@ +// +// IndicatorPoint.swift +// LineChart +// +// Created by András Samu on 2019. 09. 03.. +// Copyright © 2019. András Samu. All rights reserved. +// + +import SwiftUI + +struct IndicatorPoint: View { + var body: some View { + ZStack{ + Circle() + .fill(Colors.IndicatorKnob) + Circle() + .stroke(Color.white, style: StrokeStyle(lineWidth: 4)) + } + .frame(width: 14, height: 14) + .shadow(color: Colors.LegendColor, radius: 6, x: 0, y: 6) + } +} + +struct IndicatorPoint_Previews: PreviewProvider { + static var previews: some View { + IndicatorPoint() + } +} diff --git a/Sources/SwiftUICharts/LineChart/Legend.swift b/Sources/SwiftUICharts/LineChart/Legend.swift new file mode 100644 index 00000000..a706c0d5 --- /dev/null +++ b/Sources/SwiftUICharts/LineChart/Legend.swift @@ -0,0 +1,70 @@ +// +// Legend.swift +// LineChart +// +// Created by András Samu on 2019. 09. 02.. +// Copyright © 2019. András Samu. All rights reserved. +// + +import SwiftUI + +struct Legend: View { + @ObservedObject var data: ChartData + @Binding var frame: CGRect + @Binding var hideHorizontalLines: Bool + + var stepWidth: CGFloat { + return frame.size.width / CGFloat(data.points.count-1) + } + var stepHeight: CGFloat { + return frame.size.height / CGFloat(data.points.max()! + data.points.min()!) + } + + var body: some View { + ZStack(alignment: .topLeading){ + ForEach((0...4), id: \.self) { height in + HStack(alignment: .center){ + Text("\(self.getYLegend()![height])").offset(x: 0, y: (self.frame.height-CGFloat(self.getYLegend()![height])*self.stepHeight)-(self.frame.height/2)) + .foregroundColor(Colors.LegendText) + .font(.caption) + self.line(atHeight: CGFloat(self.getYLegend()![height]), width: self.frame.width) + .stroke(Colors.LegendColor, style: StrokeStyle(lineWidth: 1.5, lineCap: .round, dash: [5,height == 0 ? 0 : 10])) + .opacity((self.hideHorizontalLines && height != 0) ? 0 : 1) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + .animation(.easeOut(duration: 0.2)) + .clipped() + } + + } + + } + } + + func line(atHeight: CGFloat, width: CGFloat) -> Path { + var hLine = Path() + hLine.move(to: CGPoint(x:5, y: atHeight*stepHeight)) + hLine.addLine(to: CGPoint(x: width, y: atHeight*stepHeight)) + return hLine + } + + func getYLegend() -> [Int]? { + guard let max = data.points.max() else { return nil } + guard let min = data.points.min() else { return nil } + if(min > 0){ + let upperBound = ((max/10)+1) * 10 + let step = upperBound/4 + return [step * 0,step * 1, step * 2, step * 3, step * 4] + } + + return nil + } +} + +struct Legend_Previews: PreviewProvider { + static var previews: some View { + GeometryReader{ geometry in + Legend(data: TestData.data, frame: .constant(geometry.frame(in: .local)), hideHorizontalLines: .constant(false)) + }.frame(width: 320, height: 200) + } +} diff --git a/Sources/SwiftUICharts/LineChart/Line.swift b/Sources/SwiftUICharts/LineChart/Line.swift new file mode 100644 index 00000000..363c4085 --- /dev/null +++ b/Sources/SwiftUICharts/LineChart/Line.swift @@ -0,0 +1,159 @@ +// +// Line.swift +// LineChart +// +// Created by András Samu on 2019. 08. 30.. +// Copyright © 2019. András Samu. All rights reserved. +// + +import SwiftUI + +struct Line: View { + @ObservedObject var data: ChartData + @Binding var frame: CGRect + @Binding var touchLocation: CGPoint + @Binding var showIndicator: Bool + @State private var showFull: Bool = false + @State var showBackground: Bool = true + + var stepWidth: CGFloat { + return frame.size.width / CGFloat(data.points.count-1) + } + var stepHeight: CGFloat { + return frame.size.height / CGFloat(data.points.max()! + data.points.min()!) + } + var path: Path { + return Path.quadCurvedPathWithPoints(points: data.points, step: CGPoint(x: stepWidth, y: stepHeight)) + } + var closedPath: Path { + return Path.quadClosedCurvedPathWithPoints(points: data.points, step: CGPoint(x: stepWidth, y: stepHeight)) + } + + var body: some View { + ZStack { + if(self.showFull && self.showBackground){ + self.closedPath + .fill(LinearGradient(gradient: Gradient(colors: [Colors.GradientUpperBlue, .white]), startPoint: .bottom, endPoint: .top)) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + .transition(.opacity) + .animation(.easeIn(duration: 1.6)) + } + 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)) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + .animation(.easeOut(duration: 1.2)) + .onAppear(){ + self.showFull.toggle() + }.drawingGroup() + if(self.showIndicator) { + IndicatorPoint() + .position(self.getClosestPointOnPath(touchLocation: self.touchLocation)) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + } + } + } + + func getClosestPointOnPath(touchLocation: CGPoint) -> CGPoint { + let percentage:CGFloat = min(max(touchLocation.x,0)/self.frame.width,1) + let closest = self.path.percentPoint(percentage) + return closest + } + +} + +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:[Int], step:CGPoint) -> Path { + var path = Path() + var p1 = CGPoint(x: 0, y: CGFloat(points[0])*step.y) + path.move(to: p1) + if(points.count < 2){ + path.addLine(to: CGPoint(x: step.x, y: step.y*CGFloat(points[1]))) + return path + } + for pointIndex in 1.. Path { + var path = Path() + path.move(to: .zero) + var p1 = CGPoint(x: 0, y: CGFloat(points[0])*step.y) + path.addLine(to: p1) + if(points.count < 2){ + path.addLine(to: CGPoint(x: step.x, y: step.y*CGFloat(points[1]))) + return path + } + for pointIndex in 1.. CGPoint { + // percent difference between points + let diff: CGFloat = 0.001 + let comp: CGFloat = 1 - diff + + // handle limits + let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent) + + let f = pct > comp ? comp : pct + let t = pct > comp ? 1 : pct + diff + let tp = self.trimmedPath(from: f, to: t) + + return CGPoint(x: tp.boundingRect.midX, y: tp.boundingRect.midY) + } + +} + +struct Line_Previews: PreviewProvider { + static var previews: some View { + GeometryReader{ geometry in + Line(data: TestData.data, frame: .constant(geometry.frame(in: .local)), touchLocation: .constant(CGPoint(x: 300, y: 12)), showIndicator: .constant(true)) + }.frame(width: 320, height: 160) + } +} diff --git a/Sources/SwiftUICharts/LineChart/LineChartView.swift b/Sources/SwiftUICharts/LineChart/LineChartView.swift new file mode 100644 index 00000000..70d904bc --- /dev/null +++ b/Sources/SwiftUICharts/LineChart/LineChartView.swift @@ -0,0 +1,107 @@ +// +// LineCard.swift +// LineChart +// +// Created by András Samu on 2019. 08. 31.. +// Copyright © 2019. András Samu. All rights reserved. +// + +import SwiftUI + +struct LineChartView: View { + let selectionFeedbackGenerator = UISelectionFeedbackGenerator() + @ObservedObject var data:ChartData + public var title: String + public var legend: String? + public var style: ChartStyle + @State private var touchLocation:CGPoint = .zero + @State private var showIndicatorDot: Bool = false + @State private var currentValue: Int = 2 { + didSet{ + if (oldValue != self.currentValue && showIndicatorDot) { + selectionFeedbackGenerator.selectionChanged() + } + + } + } + let frame = CGSize(width: 180, height: 120) + + public init(data: [Int], title: String, legend: String? = nil, style: ChartStyle = Styles.lineChartStyleOne ){ + self.data = ChartData(points: data) + self.title = title + self.legend = legend + self.style = style + } + + var body: some View { + ZStack(alignment: .center){ + RoundedRectangle(cornerRadius: 20).fill(self.style.backgroundColor).frame(width: frame.width, height: 240, alignment: .center).shadow(radius: 8) + VStack(alignment: .leading){ + if(!self.showIndicatorDot){ + VStack(alignment: .leading, spacing: 8){ + Text(self.title).font(.title).bold().foregroundColor(self.style.textColor) + if (self.legend != nil){ + Text(self.legend!).font(.callout).foregroundColor(self.style.legendTextColor) + } + HStack { + Image(systemName: "arrow.up") + Text("14%") + } + } + .transition(.opacity) + .animation(.easeIn(duration: 0.1)) + .padding([.leading, .top]) + }else{ + HStack{ + Spacer() + Text("\(self.currentValue)") + .font(.system(size: 41, weight: .bold, design: .default)) + .offset(x: 0, y: 30) + 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) + } + .frame(width: frame.width, height: frame.height) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .offset(x: 0, y: 0) + }.frame(width: self.style.chartFormSize.width, height: self.style.chartFormSize.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 + }) + ) + } + + func getClosestDataPoint(toPoint: CGPoint, width:CGFloat, height: CGFloat) -> CGPoint { + let stepWidth: CGFloat = width / CGFloat(data.points.count-1) + let stepHeight: CGFloat = height / CGFloat(data.points.max()! + data.points.min()!) + + let index:Int = Int(round((toPoint.x)/stepWidth)) + if (index >= 0 && index < data.points.count){ + self.currentValue = self.data.points[index] + return CGPoint(x: CGFloat(index)*stepWidth, y: CGFloat(self.data.points[index])*stepHeight) + } + return .zero + } +} + +struct WidgetView_Previews: PreviewProvider { + static var previews: some View { + Group { + LineChartView(data: [8,23,54,32,12,37,7,23,43], title: "Line chart", legend: "Basic") + .environment(\.colorScheme, .light) + } + } +} diff --git a/Sources/SwiftUICharts/PieChartCell.swift b/Sources/SwiftUICharts/PieChart/PieChartCell.swift similarity index 100% rename from Sources/SwiftUICharts/PieChartCell.swift rename to Sources/SwiftUICharts/PieChart/PieChartCell.swift diff --git a/Sources/SwiftUICharts/PieChartRow.swift b/Sources/SwiftUICharts/PieChart/PieChartRow.swift similarity index 100% rename from Sources/SwiftUICharts/PieChartRow.swift rename to Sources/SwiftUICharts/PieChart/PieChartRow.swift diff --git a/Sources/SwiftUICharts/PieChartView.swift b/Sources/SwiftUICharts/PieChart/PieChartView.swift similarity index 56% rename from Sources/SwiftUICharts/PieChartView.swift rename to Sources/SwiftUICharts/PieChart/PieChartView.swift index f7ed0625..67ab6372 100644 --- a/Sources/SwiftUICharts/PieChartView.swift +++ b/Sources/SwiftUICharts/PieChart/PieChartView.swift @@ -12,49 +12,48 @@ public struct PieChartView : View { public var data: [Int] public var title: String public var legend: String? - public var backgroundColor:Color - public var accentColor:Color - - public init(data: [Int], title: String, legend: String? = nil, backgroundColor:Color = Colors.color3,accentColor:Color = Colors.color3Accent){ + public var style: ChartStyle + public init(data: [Int], title: String, legend: String? = nil, style: ChartStyle = Styles.pieChartStyleOne ){ self.data = data self.title = title self.legend = legend - self.backgroundColor = backgroundColor - self.accentColor = accentColor + self.style = style } public var body: some View { ZStack{ Rectangle() - .fill(self.backgroundColor) + .fill(self.style.backgroundColor) .cornerRadius(20) + .shadow(color: Color.gray, radius: 12) VStack(alignment: .leading){ HStack{ Text(self.title) .font(.headline) + .foregroundColor(self.style.textColor) Spacer() Image(systemName: "chart.pie.fill") .imageScale(.large) - .foregroundColor(self.accentColor) - }.padding() - PieChartRow(data: data, backgroundColor: self.backgroundColor, accentColor: self.accentColor) - .foregroundColor(self.accentColor).padding(self.legend != nil ? 0 : 12).offset(y:self.legend != nil ? 0 : -10) + .foregroundColor(self.style.legendTextColor) + }.padding() + PieChartRow(data: data, backgroundColor: self.style.backgroundColor, accentColor: self.style.accentColor) + .foregroundColor(self.style.accentColor).padding(self.legend != nil ? 0 : 12).offset(y:self.legend != nil ? 0 : -10) if(self.legend != nil) { Text(self.legend!) .font(.headline) - .foregroundColor(self.accentColor) + .foregroundColor(self.style.legendTextColor) .padding() } } - }.frame(width: 200, height: 240) + }.frame(width: self.style.chartFormSize.width, height: self.style.chartFormSize.height) } } #if DEBUG struct PieChartView_Previews : PreviewProvider { static var previews: some View { - PieChartView(data:[56,78,53], title: "Title", legend: "Legend") + PieChartView(data:[56,78,53,65,54], title: "Title", legend: "Legend") } } #endif diff --git a/chartforms.png b/chartforms.png deleted file mode 100644 index 918ee10b..00000000 Binary files a/chartforms.png and /dev/null differ diff --git a/chartview.gif b/chartview.gif deleted file mode 100644 index 028bb85a..00000000 Binary files a/chartview.gif and /dev/null differ diff --git a/showcase1.gif b/showcase1.gif new file mode 100644 index 00000000..52bba15a Binary files /dev/null and b/showcase1.gif differ diff --git a/showcase2.gif b/showcase2.gif new file mode 100644 index 00000000..81a85c64 Binary files /dev/null and b/showcase2.gif differ diff --git a/showcase3.gif b/showcase3.gif new file mode 100644 index 00000000..497fed2a Binary files /dev/null and b/showcase3.gif differ diff --git a/showcase4.png b/showcase4.png new file mode 100644 index 00000000..a58c861f Binary files /dev/null and b/showcase4.png differ diff --git a/showcase5.png b/showcase5.png new file mode 100644 index 00000000..319cd116 Binary files /dev/null and b/showcase5.png differ