Compare commits

..

10 Commits

Author SHA1 Message Date
Andras Samu 94a98ee2c7 fixed public property 2019-09-30 10:28:01 +02:00
Andras Samu da015797dd fixed midnight green shade 2019-09-19 15:54:10 +02:00
Andras Samu 617297cbcd updated color preset names 2019-09-19 15:48:35 +02:00
Andras Samu 3909818a3f Add files via upload 2019-09-19 15:41:06 +02:00
Andras Samu df24b3d1c2 Update README.md 2019-09-19 15:40:00 +02:00
Andras Samu 07a16d5548 Update README.md 2019-09-19 15:39:20 +02:00
Andras Samu 4512fd0d3e Added midnight green color styles 2019-09-19 14:56:03 +02:00
Andras Samu 8a54155e47 Added public modifier 2019-09-11 22:08:33 +02:00
Andras Samu 8197fd6ae1 Added new interactive Bar chart
Added new Line chart (also interactive)
Added new color schemes
2019-09-11 22:01:11 +02:00
Andras Samu d6682253bd readme corrected 2019-07-19 09:58:05 +02:00
24 changed files with 762 additions and 190 deletions
+63 -23
View File
@@ -2,12 +2,12 @@
Swift package for displaying charts effortlessly.
![SwiftUI Charts](./chartview.gif "SwiftUI Charts")
![Chart forms](./chartforms.png "Chart forms")
![SwiftUI Charts](./showcase1.gif "SwiftUI Charts")
It supports currently:
* barcharts
* piecharts
It supports:
* Line charts
* Bar charts
* Pie charts
### Installation:
@@ -21,37 +21,77 @@ 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
### Size format (only for bar charts yet!)
Can be
* small
* medium
* large
* second gradient color
* chart form size
* text color
* legend text color
```swift
ChartView(data: [12,17,24,33,23,56], title: "Chart two", form: Form.small)
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)
```
### 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 access built-in styles:
```swift
BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", style: Styles.barChartMidnightGreen)
```
#### All styles available as a preset:
* barChartStyleOrangeLight
* barChartStyleOrangeDark
* barChartStyleNeonBlueLight
* barChartStyleNeonBlueDark
* barChartMidnightGreenLight
* barChartMidnightGreenDark
![Midnightgreen](./midnightgreen.gif "Midnightgreen")
![Custom Charts](./showcase5.png "Custom Charts")
## Pie charts
![Pie Charts](./showcase4.png "Pie Charts")
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
```
@@ -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
@@ -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..<self.data.count) { i in
BarChartCell(value: Double(self.data[i])/Double(self.maxValue), index: i, width: Float(geometry.frame(in: .local).width - 22), numberOfDataPoints: self.data.count, accentColor: self.accentColor, secondGradientAccentColor: self.secondGradientAccentColor, 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)
}
}
.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
@@ -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
-38
View File
@@ -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)
}
}
-34
View File
@@ -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..<self.data.count) { i in
ChartCell(value: Double(self.data[i])/Double(self.maxValue), index: i, width: Float(geometry.frame(in: .local).width - 22), numberOfDataPoints: self.data.count)
}
}
.padding([.trailing,.leading], 13)
}
}
}
#if DEBUG
struct ChartRow_Previews : PreviewProvider {
static var previews: some View {
ChartRow(data: [8,23,54,32,12,37,7])
}
}
#endif
-76
View File
@@ -1,76 +0,0 @@
//
// ChartView.swift
// ChartView
//
// Created by András Samu on 2019. 06. 12..
// Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI
public struct Form {
public static let small = CGSize(width:180, height:120)
public static let medium = CGSize(width:180, height:240)
public static let large = CGSize(width:360, height:120)
public static let detail = CGSize(width:180, height:120)
}
public struct ChartView : View {
public var data: [Int]
public var title: String
public var legend: String?
public var backgroundColor:Color
public var accentColor:Color
public var formSize:CGSize
var isFullWidth:Bool {
return self.formSize == Form.large
}
public init(data: [Int], title: String, legend: String? = nil,backgroundColor:Color = Colors.color1,accentColor:Color = Colors.color1Accent, form: CGSize = Form.medium ){
self.data = data
self.title = title
self.legend = legend
self.backgroundColor = backgroundColor
self.accentColor = accentColor
self.formSize = form
}
public var body: some View {
ZStack{
Rectangle()
.fill(self.backgroundColor)
.cornerRadius(20)
VStack(alignment: .leading){
HStack{
Text(self.title)
.font(.headline)
if(self.formSize == Form.large && self.legend != nil) {
Text(self.legend!)
.font(.callout)
.foregroundColor(self.accentColor)
}
Spacer()
Image(systemName: "waveform.path.ecg")
.imageScale(.large)
.foregroundColor(self.accentColor)
}.padding()
ChartRow(data: data)
.foregroundColor(self.accentColor)
.clipped()
if self.legend != nil && self.formSize == Form.medium {
Text(self.legend!)
.font(.headline)
.foregroundColor(self.accentColor)
.padding()
}
}
}.frame(minWidth: self.formSize.width, maxWidth: self.isFullWidth ? .infinity : self.formSize.width, minHeight: self.formSize.height, maxHeight: self.formSize.height)
}
}
#if DEBUG
struct ChartView_Previews : PreviewProvider {
static var previews: some View {
ChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", legend: "Legendary")
}
}
#endif
+165
View File
@@ -0,0 +1,165 @@
//
// 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")
public static let OrangeStart:Color = Color(hexString: "#FF782C")
public static let OrangeEnd:Color = Color(hexString: "#EC2301")
public static let LegendText:Color = Color(hexString: "#A7A6A8")
public static let LegendColor:Color = Color(hexString: "#E8E7EA")
public static let IndicatorKnob:Color = Color(hexString: "#FF57A6")
public static let GradientUpperBlue:Color = Color(hexString: "#C2E8FF")
public static let GradinetUpperBlue1:Color = Color(hexString: "#A8E1FF")
public static let GradientPurple:Color = Color(hexString: "#7B75FF")
public static let GradientNeonBlue:Color = Color(hexString: "#6FEAFF")
public static let GradientLowerBlue:Color = Color(hexString: "#F1F9FF")
public static let DarkPurple:Color = Color(hexString: "#1B205E")
public static let BorderBlue:Color = Color(hexString: "#4EBCFF")
}
public struct Styles {
public static let lineChartStyleOne = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
chartFormSize: Form.medium,
textColor: Color.black,
legendTextColor: Color.gray)
public static let barChartStyleOrangeLight = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
chartFormSize: Form.medium,
textColor: Color.black,
legendTextColor: Color.gray)
public static let barChartStyleOrangeDark = ChartStyle(
backgroundColor: Color.black,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
chartFormSize: Form.medium,
textColor: Color.white,
legendTextColor: Color.gray)
public static let barChartStyleNeonBlueLight = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.GradientNeonBlue,
secondGradientColor: Colors.GradientPurple,
chartFormSize: Form.medium,
textColor: Color.black,
legendTextColor: Color.gray)
public static let barChartStyleNeonBlueDark = ChartStyle(
backgroundColor: Color.black,
accentColor: Colors.GradientNeonBlue,
secondGradientColor: Colors.GradientPurple,
chartFormSize: Form.medium,
textColor: Color.white,
legendTextColor: Color.gray)
public static let barChartMidnightGreenDark = ChartStyle(
backgroundColor: Color(hexString: "#36534D"), //3B5147, 313D34
accentColor: Color(hexString: "#FFD603"),
secondGradientColor: Color(hexString: "#FFCA04"),
chartFormSize: Form.medium,
textColor: Color.white,
legendTextColor: Color(hexString: "#D2E5E1"))
public static let barChartMidnightGreenLight = ChartStyle(
backgroundColor: Color.white,
accentColor: Color(hexString: "#84A094"), //84A094 , 698378
secondGradientColor: Color(hexString: "#50675D"),
chartFormSize: Form.medium,
textColor: Color.black,
legendTextColor:Color.gray)
public static let pieChartStyleOne = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
chartFormSize: Form.medium,
textColor: Color.black,
legendTextColor: Color.gray)
}
public struct Form {
public static let small = CGSize(width:180, height:120)
public static let medium = CGSize(width:180, height:240)
public static let large = CGSize(width:360, height:120)
public static let detail = CGSize(width:180, height:120)
}
public struct ChartStyle {
public var backgroundColor: Color
public var accentColor: Color
public var secondGradientColor: Color
public var chartFormSize: CGSize
public var textColor: Color
public var legendTextColor: Color
public init(backgroundColor: Color, accentColor: Color, secondGradientColor: Color, chartFormSize: CGSize, textColor: Color, legendTextColor: Color){
self.backgroundColor = backgroundColor
self.accentColor = accentColor
self.secondGradientColor = secondGradientColor
self.chartFormSize = chartFormSize
self.textColor = textColor
self.legendTextColor = legendTextColor
}
public init(formSize: CGSize){
self.backgroundColor = Color.white
self.accentColor = Colors.OrangeStart
self.secondGradientColor = Colors.OrangeEnd
self.chartFormSize = formSize
self.legendTextColor = Color.gray
self.textColor = Color.black
}
}
class ChartData: ObservableObject {
@Published var points: [Int] = [Int]()
@Published var currentPoint: Int? = nil
init(points:[Int]) {
self.points = points
}
}
class TestData{
static public var data:ChartData = ChartData(points: [37,72,51,22,39,47,66,85,50])
}
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)
}
}
@@ -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()
}
}
@@ -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)
}
}
+159
View File
@@ -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..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]))
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:[Int], step:CGPoint) -> 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..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]))
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
}
func percentPoint(_ percent: CGFloat) -> 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)
}
}
@@ -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
public 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
}
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: 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)
}
}
}
@@ -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
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB