Compare commits

..

57 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
xspyhack 88db9aeafe Fix line chart view indicator point (#40) 2020-01-22 13:56:03 +01:00
Kevin Fowler 37c51d9b46 Fix llvm segfault when archiving SwiftUICharts (#36)
I added ChartView/SwiftUICharts to my iphone project. It worked as
expected until I tried to archive the application for distribution.

In the archive step, compilation fails with a segmentation fault:
    1.	While running pass #48703 SILFunctionTransform "GenericSpecializer" on SILFunction "@$s13SwiftUICharts9ChartDataC6pointsACSayxG_tcSBRzlufcSd_Tg5".
     for 'init(points:)' (at /Users/kfowler/projects/ChartView/Sources/SwiftUICharts/Helpers.swift:134:12)
    0  swift                    0x0000000113b94a63 PrintStackTraceSignalHandler(void*) + 51
    1  swift                    0x0000000113b94236 SignalHandler(int) + 358
    2  libsystem_platform.dylib 0x00007fff6bd7d42d _sigtramp + 29
    3  libsystem_platform.dylib 0x0000000800000001 _sigtramp + 2485660657
    4  swift                    0x000000010ffd800c swift::ReabstractionInfo::prepareAndCheck(swift::ApplySite, swift::SILFunction*, swift::SubstitutionMap, swift::OptRemark::Emitter*) + 572
    5  swift                    0x000000011002abda swift::ReabstractionInfo::ReabstractionInfo(swift::ApplySite, swift::SILFunction*, swift::SubstitutionMap, swift::IsSerialized_t, bool, swift::OptRemark::Emitter*) + 122
    6  swift                    0x0000000110034c35 swift::trySpecializeApplyOfGeneric(swift::SILOptFunctionBuilder&, swift::ApplySite, llvm::SmallSetVector<swift::SILInstruction*, 8u>&, llvm::SmallVectorImpl<swift::SILFunction*>&, swift::OptRemark::Emitter&) + 1653
    7  swift                    0x000000010ff0d731 (anonymous namespace)::GenericSpecializer::run() + 2673
    8  swift                    0x000000010fe86f3e swift::SILPassManager::execute() + 4606
    9  swift                    0x000000010fae596b swift::CompilerInstance::performSILProcessing(swift::SILModule*, swift::UnifiedStatsReporter*) + 6379
    10 swift                    0x000000010f7ddec5 performCompile(swift::CompilerInstance&, swift::CompilerInvocation&, llvm::ArrayRef<char const*>, int&, swift::FrontendObserver*, swift::UnifiedStatsReporter*) + 33925
    11 swift                    0x000000010f7d2234 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 6820
    12 swift                    0x000000010f75f733 main + 1219
    13 libdyld.dylib            0x00007fff6bb847fd start + 1
error: Segmentation fault: 11 (in target 'SwiftUICharts' from project 'SwiftUICharts')

This change resolves the issue.
2020-01-17 12:29:47 +01:00
Daniel Barclay 75df39fc1f Fix animation undoing itself by multiple calls of toggle() (#35) 2020-01-17 12:29:03 +01:00
Leanne 6b5affa46e Update README.md (#33) 2020-01-16 10:47:00 +01:00
Andras Samu 1e362b9eea Added Label,Value pairs so you can display a label for each point in Bar chart, added ability to change ecg image in the corner, added Generic number types to ChartData initialiser 2020-01-11 12:36:42 +01:00
Andras Samu f7d9895e36 Merge remote-tracking branch 'refs/remotes/origin/master' 2020-01-07 09:57:32 +01:00
Andras Samu 04b6e385ea Fixed chart clipping, and value animation issue 2020-01-07 09:56:16 +01:00
Tieda 9f2e3d32df Fixed Xcode typo in README (#32) 2020-01-05 11:17:19 +01:00
Ricky Cai 524aec2a04 Fixed Issue 28 (#29)
Changed Int to Double in quadCurvedPathWithPoints and quadClosedCurvedPathWithPoints.
2019-12-28 09:00:48 +01:00
Andras Samu 03f90728b4 solved line view negative numbers, also when it crashed 0 or 1 element data set 2019-12-27 21:37:28 +01:00
Andras Samu 0d95dbd3d4 Fixed: Form redeclaration as ChartForm issue #23 2019-12-12 14:31:07 +01:00
Steven Zweier fd14ca2327 Allow graphs to accept Double (#19) 2019-11-24 21:20:47 +01:00
Andras Samu 20fb782a3e quick fix for 0 elements in line view 2019-11-13 10:16:39 +01:00
Andras Samu 9fa7e20221 Merge remote-tracking branch 'refs/remotes/origin/master' 2019-11-13 10:00:49 +01:00
Andras Samu b5e3aa897c added self 2019-11-13 09:48:40 +01:00
Andras Samu 1b45a6a922 Update README.md 2019-11-11 21:53:57 +01:00
Andras Samu d3d0b086f1 updated readme 2019-11-11 21:50:33 +01:00
Andras Samu 6cc43d9dc0 fixed legend in dark mode 2019-11-11 21:20:13 +01:00
Andras Samu e081d3a88d fixed loupe 2019-11-11 21:13:10 +01:00
Andras Samu e5c309eac4 set to public 2019-11-11 20:41:42 +01:00
Andras Samu 3f542f92e3 Added LineView 2019-11-11 20:29:41 +01:00
sy1995 7a9f013631 Hi, I find that the LineChartView has a value is static (#15)
* add rateValue to LineChart

* modify

* modify rate value to linechart

* modify

* modify
2019-11-11 15:58:21 +01:00
Andras Samu 8dffe79e28 Merge pull request #10 from jufabeck2202/Uint64
'scanHexInt32' was deprecated in iOS 13.0
2019-10-15 09:40:19 +02:00
Julian Beck d5a1c0065c 'scanHexInt32' was deprecated in iOS 13.0 2019-10-10 21:52:21 +02:00
Andras Samu b9761d3eb9 Update README.md 2019-10-07 10:51:25 +02:00
Andras Samu c3c0a8a7c4 Merge pull request #8 from WayneEld/issue/print-line-removed
Removal of index print line.
2019-10-04 11:46:05 +02:00
Wayne Eldridge b597faac76 Removal of index print line. 2019-10-03 20:02:42 +02:00
Andras Samu fc1099f486 Update README.md 2019-09-30 20:45:32 +02:00
Andras Samu 94950db4d5 added watchos image 2019-09-30 20:42:30 +02:00
Andras Samu 251a830281 Added watchOS support 2019-09-30 20:41:04 +02:00
Andras Samu b2b0b83b4b fixed style error 2019-09-30 10:31:34 +02:00
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
Andras Samu 4c48427383 Added small, medium and large size formats 2019-07-19 09:53:09 +02:00
Andras Samu fcc2870b14 created license 2019-07-18 15:23:31 +02:00
36 changed files with 1869 additions and 182 deletions
@@ -10,5 +10,18 @@
<integer>0</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>SwiftUICharts</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>SwiftUIChartsTests</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>SwiftUICharts.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
</dict>
</dict>
</plist>
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Andras Samu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1 -1
View File
@@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "SwiftUICharts",
platforms: [
.iOS(.v13),
.iOS(.v13),.watchOS(.v6)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
+163 -13
View File
@@ -2,17 +2,18 @@
Swift package for displaying charts effortlessly.
![SwiftUI Charts](./chartview.gif "SwiftUI Charts")
![SwiftUI Charts](./Resources/showcase1.gif "SwiftUI Charts")
It supports currently:
* barcharts
* piecharts
It supports:
* Line charts
* Bar charts
* Pie charts
### Installation:
It requires iOS 13 and xCode 11!
It requires iOS 13 and Xcode 11!
In xCode got to `File -> Swift Packages -> Add Package Dependency` and paste inthe repo's url: `https://github.com/AppPear/ChartView`
In Xcode got to `File -> Swift Packages -> Add Package Dependency` and paste inthe repo's url: `https://github.com/AppPear/ChartView`
### Usage:
@@ -20,23 +21,172 @@ 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:
### Demo
Added an example project, with **iOS, watchOS** target: https://github.com/AppPear/ChartViewDemo
## Line charts
**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
ChartView(data: [8,23,54,32,12,37,7,23,43], title: "Barchart")
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")
```swift
LineView(data: [8,23,54,32,12,37,7,23,43], title: "Line chart", legend: "Full screen") // legend is optional, use optional .padding()
```
Piechart:
Adopts to dark mode automatically
![Line Charts](./Resources/showcase3.gif "Line Charts")
You can add your custom darkmode style by specifying:
```swift
PieChartView(data:[43,56,78,34], title: "Piechart")
let myCustomStyle = ChartStyle(...)
let myCutsomDarkModeStyle = ChartStyle(...)
myCustomStyle.darkModeStyle = myCutsomDarkModeStyle
```
You can optionally configure:
* legend
**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
LineChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", legend: "Legendary") // legend is optional
```
**Turn drop shadow off by adding to the Initialiser: `dropShadow: false`**
## Bar charts
![Bar Charts](./Resources/showcase2.gif "Bar Charts")
**[New feature] you can display labels also along values and points for each bar to descirbe your data better!**
**Bar chart is interactive, so you can drag across to reveal the data points**
You can add a bar chart with the following code:
Labels and points:
```swift
BarChartView(data: ChartData(values: [("2018 Q4",63150), ("2019 Q1",50900), ("2019 Q2",77550), ("2019 Q3",79600), ("2019 Q4",92550)]), title: "Sales", legend: "Quarterly") // legend is optional
```
Only points:
```swift
BarChartView(data: ChartData(points: [8,23,54,32,12,37,7,23,43]), title: "Title", legend: "Legendary") // legend is optional
```
**ChartData** structure
Stores values in data pairs (actually tuple): `(String,Double)`
* you can have duplicate values
* keeps the data order
You can initialise ChartData multiple ways:
* For integer values: `ChartData(points: [8,23,54,32,12,37,7,23,43])`
* For floating point values: `ChartData(points: [2.34,3.14,4.56])`
* For label,value pairs: `ChartData(values: [("2018 Q4",63150), ("2019 Q1",50900)])`
You can add different formats:
* Small `ChartForm.small`
* Medium `ChartForm.medium`
* Large `ChartForm.large`
```swift
BarChartView(data: ChartData(points: [8,23,54,32,12,37,7,23,43]), title: "Title", form: ChartForm.small)
```
For floating point numbers, you can set a custom specifier:
```swift
BarChartView(data: ChartData(points:[1.23,2.43,3.37]) ,title: "A", valueSpecifier: "%.2f")
```
For integers you can disable by passing: `valueSpecifier: "%.0f"`
You can set your custom image in the upper right corner by passing in the initialiser: `cornerImage:Image(systemName: "waveform.path.ecg")`
**Turn drop shadow off by adding to the Initialiser: `dropShadow: false`**
### You can customize styling of the chart with a ChartStyle object:
Customizable:
* background color
* accent color
* second gradient color
* text color
* legend text color
```swift
ChartView(data: [12,17,24,33,36,31,27,23,14], title: "Title", legend: "Legend", backgroundColor:Color(red: 226.0/255.0, green: 250.0/255.0, blue: 231.0/255.0) , accentColor:Color(red: 114.0/255.0, green: 191.0/255.0, blue: 130.0/255.0))
let chartStyle = ChartStyle(backgroundColor: Color.black, accentColor: Colors.OrangeStart, secondGradientColor: Colors.OrangeEnd, chartFormSize: ChartForm.medium, textColor: Color.white, legendTextColor: Color.white )
...
BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", style: chartStyle)
```
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](./Resources/midnightgreen.gif "Midnightgreen")
![Custom Charts](./Resources/showcase5.png "Custom Charts")
### You can customize the size of the chart with a ChartForm object:
**ChartForm**
* `.small`
* `.medium`
* `.large`
* `.detail`
```swift
BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", form: ChartForm.small)
```
### WatchOS support for Bar charts:
![Pie Charts](./Resources/watchos1.png "Pie Charts")
## Pie charts
![Pie Charts](./Resources/showcase4.png "Pie Charts")
You can add a pie chart with the following code:
```swift
PieChartView(data: [8,23,54,32], title: "Title", legend: "Legendary") // legend is optional
```
**Turn drop shadow off by adding to the Initialiser: `dropShadow: false`**
Binary file not shown.

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

@@ -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,26 +16,29 @@ public struct ChartCell : View {
var cellWidth: Double {
return Double(width)/(Double(numberOfDataPoints) * 1.5)
}
var accentColor: Color
var gradient: GradientColor?
@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?.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)
.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, gradient: nil, touchLocation: .constant(-1))
}
}
#endif
@@ -0,0 +1,50 @@
//
// 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: [Double]
var accentColor: Color
var gradient: GradientColor?
var maxValue: Double {
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, 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())
}
}
.padding([.top, .leading, .trailing], 10)
}
}
func normalizedValue(index: Int) -> Double {
return Double(self.data[index])/Double(self.maxValue)
}
}
#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,148 @@
//
// 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 {
@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
public var valueSpecifier:String
@State private var touchLocation: CGFloat = -1.0
@State private var showValue: Bool = false
@State private var showLabelValue: Bool = false
@State private var currentValue: Double = 0 {
didSet{
if(oldValue != self.currentValue && self.showValue) {
HapticFeedback.playSelection()
}
}
}
var isFullWidth:Bool {
return self.formSize == ChartForm.large
}
public init(data:ChartData, title: String, legend: String? = nil, style: ChartStyle = Styles.barChartStyleOrangeLight, form: CGSize? = ChartForm.medium, dropShadow: Bool? = true, cornerImage:Image? = Image(systemName: "waveform.path.ecg"), valueSpecifier: String? = "%.1f"){
self.data = data
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!
self.valueSpecifier = valueSpecifier!
}
public var body: some View {
ZStack{
Rectangle()
.fill(self.colorScheme == .dark ? self.darkModeStyle.backgroundColor : self.style.backgroundColor)
.cornerRadius(20)
.shadow(color: self.style.dropShadowColor, radius: self.dropShadow ? 8 : 0)
VStack(alignment: .leading){
HStack{
if(!showValue){
Text(self.title)
.font(.headline)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.textColor : self.style.textColor)
}else{
Text("\(self.currentValue, specifier: self.valueSpecifier)")
.font(.headline)
.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.colorScheme == .dark ? self.darkModeStyle.accentColor : self.style.accentColor)
.transition(.opacity)
.animation(.easeOut)
}
Spacer()
self.cornerImage
.imageScale(.large)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
}.padding()
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.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
.padding()
}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)
.gesture(DragGesture()
.onChanged({ value in
self.touchLocation = value.location.x/self.formSize.width
self.showValue = true
self.currentValue = self.getCurrentValue()?.1 ?? 0
if(self.data.valuesGiven && self.formSize == ChartForm.medium) {
self.showLabelValue = true
}
})
.onEnded({ value in
self.showValue = false
self.showLabelValue = false
self.touchLocation = -1
})
)
.gesture(TapGesture()
)
}
func getArrowOffset(touchLocation:CGFloat) -> Binding<CGFloat> {
let realLoc = (self.touchLocation * self.formSize.width) - 50
if realLoc < 10 {
return .constant(realLoc - 10)
}else if realLoc > self.formSize.width-110 {
return .constant((self.formSize.width-110 - realLoc) * -1)
} else {
return .constant(0)
}
}
func getLabelViewOffset(touchLocation:CGFloat) -> CGFloat {
return min(self.formSize.width-110,max(10,(self.touchLocation * self.formSize.width) - 50))
}
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]
}
}
#if DEBUG
struct ChartView_Previews : PreviewProvider {
static var previews: some View {
BarChartView(data: TestData.values ,
title: "Model 3 sales",
legend: "Quarterly",
valueSpecifier: "%.0f")
}
}
#endif
@@ -0,0 +1,46 @@
//
// LabelView.swift
// BarChart
//
// Created by Samu András on 2020. 01. 08..
// Copyright © 2020. Samu András. All rights reserved.
//
import SwiftUI
struct LabelView: View {
@Binding var arrowOffset: CGFloat
@Binding var title:String
var body: some View {
VStack{
ArrowUp().fill(Color.white).frame(width: 20, height: 12, alignment: .center).shadow(color: Color.gray, radius: 8, x: 0, y: 0).offset(x: getArrowOffset(offset:self.arrowOffset), y: 12)
ZStack{
RoundedRectangle(cornerRadius: 8).frame(width: 100, height: 32, alignment: .center).foregroundColor(Color.white).shadow(radius: 8)
Text(self.title).font(.caption).bold()
ArrowUp().fill(Color.white).frame(width: 20, height: 12, alignment: .center).zIndex(999).offset(x: getArrowOffset(offset:self.arrowOffset), y: -20)
}
}
}
func getArrowOffset(offset: CGFloat) -> CGFloat {
return max(-36,min(36, offset))
}
}
struct ArrowUp: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y: rect.height))
path.addLine(to: CGPoint(x: rect.width/2, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.closeSubpath()
return path
}
}
struct LabelView_Previews: PreviewProvider {
static var previews: some View {
LabelView(arrowOffset: .constant(0), title: .constant("Tesla model 3"))
}
}
-33
View File
@@ -1,33 +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
-60
View File
@@ -1,60 +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 ChartView : 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 = Color(red: 238.0/255.0, green: 241.0/255.0, blue: 254.0/255.0),accentColor:Color = Color(red: 66.0/255.0, green: 102.0/255.0, blue: 232.0/255.0) ){
self.data = data
self.title = title
self.legend = legend
self.backgroundColor = backgroundColor
self.accentColor = accentColor
}
public var body: some View {
ZStack{
Rectangle()
.fill(self.backgroundColor)
.cornerRadius(20)
VStack(alignment: .leading){
HStack{
Text(self.title)
.font(.headline)
Spacer()
Image(systemName: "waveform.path.ecg")
.imageScale(.large)
.foregroundColor(self.accentColor)
}.padding()
ChartRow(data: data)
.foregroundColor(self.accentColor)
.clipped()
if self.legend != nil {
Text(self.legend!)
.font(.headline)
.foregroundColor(self.accentColor)
.padding()
}
}
}.frame(width: 180, height: 240)
}
}
#if DEBUG
struct ChartView_Previews : PreviewProvider {
static var previews: some View {
ChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title")
}
}
#endif
+278
View File
@@ -0,0 +1,278 @@
//
// 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 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")
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 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,
dropShadowColor: Color.gray)
public static let barChartStyleOrangeLight = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.OrangeStart,
secondGradientColor: Colors.OrangeEnd,
textColor: Color.black,
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,
dropShadowColor: Color.gray)
public static let barChartStyleNeonBlueLight = ChartStyle(
backgroundColor: Color.white,
accentColor: Colors.GradientNeonBlue,
secondGradientColor: Colors.GradientPurple,
textColor: Color.black,
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,
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"),
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,
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.white,
legendTextColor: Color.white,
dropShadowColor: Color.gray)
}
public struct ChartForm {
#if os(watchOS)
public static let small = CGSize(width:120, height:90)
public static let medium = CGSize(width:120, height:160)
public static let large = CGSize(width:180, height:90)
public static let detail = CGSize(width:180, height:160)
#else
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)
#endif
}
public class ChartStyle {
public var backgroundColor: Color
public var accentColor: 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, dropShadowColor: Color){
self.backgroundColor = backgroundColor
self.accentColor = accentColor
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.gradientColor = GradientColors.orange
self.legendTextColor = Color.gray
self.textColor = Color.black
self.dropShadowColor = Color.gray
}
}
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))}
}
public init<N: BinaryInteger>(values:[(String,N)]){
self.points = values.map{($0.0, Double($0.1))}
self.valuesGiven = true
}
public init<N: BinaryFloatingPoint>(values:[(String,N)]){
self.points = values.map{($0.0, Double($0.1))}
self.valuesGiven = true
}
public init<N: BinaryInteger>(numberValues:[(N,N)]){
self.points = numberValues.map{(String($0.0), Double($0.1))}
self.valuesGiven = true
}
public init<N: BinaryFloatingPoint & LosslessStringConvertible>(numberValues:[(N,N)]){
self.points = numberValues.map{(String($0.0), Double($0.1))}
self.valuesGiven = true
}
public func onlyPoints() -> [Double] {
return self.points.map{ $0.1 }
}
}
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)])
}
extension Color {
init(hexString: String) {
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt64()
Scanner(string: hex).scanHexInt64(&int)
let r, g, b: UInt64
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)
}
}
class HapticFeedback {
#if os(watchOS)
//watchOS implementation
static func playSelection() -> Void {
WKInterfaceDevice.current().play(.click)
}
#else
//iOS implementation
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
static func playSelection() -> Void {
UISelectionFeedbackGenerator().selectionChanged()
}
#endif
}
@@ -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,99 @@
//
// 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
@Environment(\.colorScheme) var colorScheme: ColorScheme
let padding:CGFloat = 3
var stepWidth: CGFloat {
if data.points.count < 2 {
return 0
}
return frame.size.width / CGFloat(data.points.count-1)
}
var stepHeight: CGFloat {
let points = self.data.onlyPoints()
if let min = points.min(), let max = points.max(), min != max {
if (min < 0){
return (frame.size.height-padding) / CGFloat(max - min)
}else{
return (frame.size.height-padding) / CGFloat(max + min)
}
}
return 0
}
var min: CGFloat {
let points = self.data.onlyPoints()
return CGFloat(points.min() ?? 0)
}
var body: some View {
ZStack(alignment: .topLeading){
ForEach((0...4), id: \.self) { height in
HStack(alignment: .center){
Text("\(self.getYLegendSafe(height: height), specifier: "%.2f")").offset(x: 0, y: self.getYposition(height: height) )
.foregroundColor(Colors.LegendText)
.font(.caption)
self.line(atHeight: self.getYLegendSafe(height: height), width: self.frame.width)
.stroke(self.colorScheme == .dark ? Colors.LegendDarkColor : 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 getYLegendSafe(height:Int)->CGFloat{
if let legend = getYLegend() {
return CGFloat(legend[height])
}
return 0
}
func getYposition(height: Int)-> CGFloat {
if let legend = getYLegend() {
return (self.frame.height-((CGFloat(legend[height]) - min)*self.stepHeight))-(self.frame.height/2)
}
return 0
}
func line(atHeight: CGFloat, width: CGFloat) -> Path {
var hLine = Path()
hLine.move(to: CGPoint(x:5, y: (atHeight-min)*stepHeight))
hLine.addLine(to: CGPoint(x: width, y: (atHeight-min)*stepHeight))
return hLine
}
func getYLegend() -> [Double]? {
let points = self.data.onlyPoints()
guard let max = points.max() else { return nil }
guard let min = points.min() else { return nil }
let step = Double(max - min)/4
return [min+step * 0, min+step * 1, min+step * 2, min+step * 3, min+step * 4]
}
}
struct Legend_Previews: PreviewProvider {
static var previews: some View {
GeometryReader{ geometry in
Legend(data: ChartData(points: [0.2,0.4,1.4,4.5]), frame: .constant(geometry.frame(in: .local)), hideHorizontalLines: .constant(false))
}.frame(width: 320, height: 200)
}
}
+107
View File
@@ -0,0 +1,107 @@
//
// Line.swift
// LineChart
//
// Created by András Samu on 2019. 08. 30..
// Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI
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
}
return frame.size.width / CGFloat(data.points.count-1)
}
var stepHeight: CGFloat {
var min: Double?
var max: Double?
let points = self.data.onlyPoints()
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(max - min)
}else{
return (frame.size.height-padding) / CGFloat(max + min)
}
}
return 0
}
var path: Path {
let points = self.data.onlyPoints()
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 curvedLines ? Path.quadClosedCurvedPathWithPoints(points: points, step: CGPoint(x: stepWidth, y: stepHeight), globalOffset: minDataValue) : Path.closedLinePathWithPoints(points: points, step: CGPoint(x: stepWidth, y: stepHeight))
}
public 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.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(Animation.easeOut(duration: 1.2).delay(Double(self.index)*0.4))
.onAppear {
self.showFull = true
}
.onDisappear {
self.showFull = false
}
.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 closest = self.path.point(to: touchLocation.x)
return closest
}
}
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), minDataValue: .constant(nil), maxDataValue: .constant(nil))
}.frame(width: 320, height: 160)
}
}
@@ -0,0 +1,144 @@
//
// 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 {
@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) {
HapticFeedback.playSelection()
}
}
}
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") {
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!
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(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.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
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))
.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 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)
}
}
}
@@ -0,0 +1,126 @@
//
// LineView.swift
// LineChart
//
// Created by András Samu on 2019. 09. 02..
// Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI
public struct LineView: View {
@ObservedObject var data: ChartData
public var 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
@State private var indicatorLocation:CGPoint = .zero
@State private var closestPoint: CGPoint = .zero
@State private var opacity:Double = 0
@State private var currentDataNumber: Double = 0
@State private var hideHorizontalLines: Bool = false
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.valueSpecifier = valueSpecifier!
self.darkModeStyle = style.darkModeStyle != nil ? style.darkModeStyle! : Styles.lineViewDarkMode
}
public var body: some View {
GeometryReader{ geometry in
VStack(alignment: .leading, spacing: 8) {
Group{
if (self.title != nil){
Text(self.title!)
.font(.title)
.bold().foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.textColor : self.style.textColor)
}
if (self.legend != nil){
Text(self.legend!)
.font(.callout)
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
}
}.offset(x: 0, y: 20)
ZStack{
GeometryReader{ reader in
Rectangle()
.foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.backgroundColor : self.style.backgroundColor)
if(self.showLegend){
Legend(data: self.data,
frame: .constant(reader.frame(in: .local)), hideHorizontalLines: self.$hideHorizontalLines)
.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,
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
})
.onEnded({ value in
self.opacity = 0
self.hideHorizontalLines = false
})
)
}
}
}
func getClosestDataPoint(toPoint: CGPoint, width:CGFloat, height: CGFloat) -> CGPoint {
let points = self.data.onlyPoints()
let stepWidth: CGFloat = width / CGFloat(points.count-1)
let stepHeight: CGFloat = height / CGFloat(points.max()! + points.min()!)
let index:Int = Int(floor((toPoint.x-15)/stepWidth))
if (index >= 0 && index < points.count){
self.currentDataNumber = points[index]
return CGPoint(x: CGFloat(index)*stepWidth, y: CGFloat(points[index])*stepHeight)
}
return .zero
}
}
struct LineView_Previews: PreviewProvider {
static var previews: some View {
LineView(data: [8,23,54,32,12,37,7,23,43], title: "Full chart", style: Styles.lineChartStyleOne)
}
}
@@ -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)
}
}
}
@@ -0,0 +1,353 @@
//
// File.swift
//
//
// Created by xspyhack on 2020/1/21.
//
import SwiftUI
extension Path {
func trimmedPath(for percent: CGFloat) -> 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):
if start == nil {
start = to
}
point = to
case .line(let to):
ret += point.line(to: to)
point = to
case .quadCurve(let to, let control):
ret += point.quadCurve(to: to, control: control)
point = to
case .curve(let to, let control1, let control2):
ret += point.curve(to: to, control1: control1, control2: control2)
point = to
case .closeSubpath:
if let to = start {
ret += point.line(to: to)
point = to
}
start = nil
}
}
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
}
switch ele {
case .move(let to):
if to.x > maxX {
finished = true
return
}
if start == nil {
start = to
}
point = to
case .line(let to):
if to.x > maxX {
finished = true
ret += point.line(to: to, x: maxX)
return
}
ret += point.line(to: to)
point = to
case .quadCurve(let to, let control):
if to.x > maxX {
finished = true
ret += point.quadCurve(to: to, control: control, x: maxX)
return
}
ret += point.quadCurve(to: to, control: control)
point = to
case .curve(let to, let control1, let control2):
if to.x > maxX {
finished = true
ret += point.curve(to: to, control1: control1, control2: control2, x: maxX)
return
}
ret += point.curve(to: to, control1: control1, control2: control2)
point = to
case .closeSubpath:
fatalError("Can't include closeSubpath")
}
}
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 {
func point(to: CGPoint, x: CGFloat) -> CGPoint {
let a = (to.y - self.y) / (to.x - self.x)
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 {
dist += a.line(to: b, x: x)
return dist
} else if b.x == x {
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 {
dist += a.line(to: b, x: x)
return dist
} else if b.x == x {
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
value += pow(1-t, 2) * x
value += 2 * (1-t) * t * c
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
value += pow(1-t, 3) * x
value += 3 * pow(1-t, 2) * t * c1
value += 3 * (1-t) * pow(t, 2) * c2
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
}
}
@@ -12,7 +12,7 @@ struct PieSlice: Identifiable {
var id = UUID()
var startDeg: Double
var endDeg: Double
var value: Int
var value: Double
var normalizedValue: Double
}
@@ -36,12 +36,12 @@ public struct PieChartCell : View {
var accentColor:Color
public var body: some View {
path
.fill()
.foregroundColor(self.accentColor)
.overlay(path.stroke(self.backgroundColor, lineWidth: 2))
.scaleEffect(self.show ? 1 : 0)
.animation(Animation.spring().delay(Double(self.index) * 0.04))
.onAppear(){
.fill()
.foregroundColor(self.accentColor)
.overlay(path.stroke(self.backgroundColor, lineWidth: 2))
.scaleEffect(self.show ? 1 : 0)
.animation(Animation.spring().delay(Double(self.index) * 0.04))
.onAppear(){
self.show = true
}
}
@@ -58,7 +58,7 @@ struct PieChartCell_Previews : PreviewProvider {
static var previews: some View {
GeometryReader { geometry in
PieChartCell(rect: geometry.frame(in: .local),startDeg: 0.0,endDeg: 90.0, index: 0, backgroundColor: Color(red: 252.0/255.0, green: 236.0/255.0, blue: 234.0/255.0), accentColor: Color(red: 225.0/255.0, green: 97.0/255.0, blue: 76.0/255.0))
}.frame(width:100, height:100)
}.frame(width:100, height:100)
}
}
@@ -9,7 +9,7 @@
import SwiftUI
public struct PieChartRow : View {
var data: [Int]
var data: [Double]
var backgroundColor: Color
var accentColor: Color
var slices: [PieSlice] {
@@ -0,0 +1,64 @@
//
// PieChartView.swift
// ChartView
//
// Created by András Samu on 2019. 06. 12..
// Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI
public struct PieChartView : View {
public var data: [Double]
public var title: String
public var legend: String?
public var style: ChartStyle
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){
self.data = data
self.title = title
self.legend = legend
self.style = style
self.formSize = form!
self.dropShadow = dropShadow!
}
public var body: some View {
ZStack{
Rectangle()
.fill(self.style.backgroundColor)
.cornerRadius(20)
.shadow(color: self.style.dropShadowColor, radius: self.dropShadow ? 12 : 0)
VStack(alignment: .leading){
HStack{
Text(self.title)
.font(.headline)
.foregroundColor(self.style.textColor)
Spacer()
Image(systemName: "chart.pie.fill")
.imageScale(.large)
.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.style.legendTextColor)
.padding()
}
}
}.frame(width: self.formSize.width, height: self.formSize.height)
}
}
#if DEBUG
struct PieChartView_Previews : PreviewProvider {
static var previews: some View {
PieChartView(data:[56,78,53,65,54], title: "Title", legend: "Legend")
}
}
#endif
-60
View File
@@ -1,60 +0,0 @@
//
// PieChartView.swift
// ChartView
//
// Created by András Samu on 2019. 06. 12..
// Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI
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 = Color(red: 252.0/255.0, green: 236.0/255.0, blue: 234.0/255.0),accentColor:Color = Color(red: 225.0/255.0, green: 97.0/255.0, blue: 76.0/255.0)){
self.data = data
self.title = title
self.legend = legend
self.backgroundColor = backgroundColor
self.accentColor = accentColor
}
public var body: some View {
ZStack{
Rectangle()
.fill(self.backgroundColor)
.cornerRadius(20)
VStack(alignment: .leading){
HStack{
Text(self.title)
.font(.headline)
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)
if(self.legend != nil) {
Text(self.legend!)
.font(.headline)
.foregroundColor(self.accentColor)
.padding()
}
}
}.frame(width: 200, height: 240)
}
}
#if DEBUG
struct PieChartView_Previews : PreviewProvider {
static var previews: some View {
PieChartView(data:[56,78,53], title: "Title", legend: "Legend")
}
}
#endif
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB