Compare commits

...

80 Commits

Author SHA1 Message Date
Andras Samu 47052674f5 Builded with xcode 12 2020-06-28 14:52:46 +02:00
Andras Samu a36c1db2f8 Update README.md 2020-06-27 14:50:41 +02:00
Roderic Campbell ca19dd578c Cleanup the explicitly unwrapped parameters which had a default value… (#123)
* Cleanup the explicitly unwrapped parameters which had a default value anyway

* Remove print statement
2020-06-27 09:27:55 +02:00
nicolas 27e7e0dd1d Bigger scale (#113) 2020-06-16 19:43:09 +02:00
nicolas 74140af7a7 Add extra large size (#108) 2020-05-31 20:06:17 +02:00
nicolas 7568c5d40c Bug Fix - only 0 data (#109) 2020-05-30 17:18:31 +02:00
nicolas 24cf9eacb8 Update README.md (#106) 2020-05-28 18:58:06 +02:00
Andras Samu c89b1e1480 Update Package.swift 2020-05-28 18:55:26 +02:00
Andras Samu 82e8f249cc Create swift.yml 2020-05-28 18:52:44 +02:00
Andras Samu 22a38a1d40 Update README.md 2020-05-24 21:21:56 +02:00
Andras Samu b5b7c62645 Update v2Ticket.md 2020-05-24 20:50:01 +02:00
Andras Samu 3447d5c9bb Rename v2Ticket to v2Ticket.md 2020-05-24 20:47:47 +02:00
Andras Samu 640ddeb4d2 Create v2Ticket 2020-05-24 20:44:24 +02:00
Lucas Desouza ebd09f438e Fix issue templates (#92)
* fixes name of template files

* adds missing front matter
2020-05-24 17:37:35 +02:00
Andras Samu 0303c3c14d Update README.md 2020-05-24 16:40:02 +02:00
Lucas Desouza 068ea84ddf adds pr template (#90) 2020-05-22 22:15:32 +02:00
Lucas Desouza fa8e015794 adds issue templates (#88) 2020-05-22 17:52:57 +02:00
Daniel dd7a1fc9bd Line view custom gradient (#67)
* feat: Added gradient to Line init from LineView

* Making GradientColor's init method as public
2020-05-18 12:23:21 +02:00
josephwalden13 fada162030 change rateValue to optional to fix crash from force unwraps and set it to show in the chart only for non zero values (#71) 2020-05-07 11:55:35 +02:00
Pierluigi Dell'Acqua a242bd3c94 Making GradientColor's init method as public (#63)
Making the method as public, application is allowed to define custom GradientColor.
2020-04-26 16:22:29 +02:00
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
Andras Samu 0aebcf4abc Added dynamic scaling to bar-chart 2019-06-26 21:06:08 +02:00
Andras Samu ef58fd9560 screenshot 2019-06-14 15:27:22 +02:00
Andras Samu 1ab98cba42 Updated readme with instructions 2019-06-14 15:19:55 +02:00
43 changed files with 2087 additions and 194 deletions
+34
View File
@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
<!--- Provide a general summary of the issue in the Title above -->
## Description
<!--- Provide a more detailed introduction to the issue itself, and why you consider it to be a bug -->
## Expected Behavior
<!--- Tell us what should happen -->
## Actual Behavior
<!--- Tell us what happens instead -->
## Possible Fix
<!--- Not obligatory, but suggest a fix or reason for the bug -->
## Steps to Reproduce
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
## Your Environment
<!--- Include as many relevant details about the environment -->
* Version of this package used:
* Device/Simulator:
* Operating System and version:
* Link to your project:
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Ask for a new feature
title: ''
labels: ''
assignees: ''
---
<!--- Provide a general summary of the issue in the Title above -->
## Detailed Description
<!--- Provide a detailed description of the change or addition you are proposing -->
## Context
<!--- Why is this change important to you? How would you use it? -->
<!--- How can it benefit other users? -->
## Possible Implementation
<!--- Not obligatory, but suggest an idea for implementing addition or change -->
+11
View File
@@ -0,0 +1,11 @@
---
name: v2 ticket
about: Create tasks for the upcoming new version
title: ''
labels: v2
assignees: ''
---
# v2 ticket
## Ticket description:
+29
View File
@@ -0,0 +1,29 @@
<!--- Provide a general summary of your changes in the Title above -->
## Description
<!--- Describe your changes in detail -->
## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
## Screenshots (if appropriate):
## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Non-functional change (Updating Documentation, CI automation, etc..)
## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
+22
View File
@@ -0,0 +1,22 @@
name: Swift
on:
push:
branches:
- master
- new-version
pull_request:
branches:
- master
- new-version
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
@@ -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>0</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), .macOS(.v10_15)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
+201 -1
View File
@@ -1,3 +1,203 @@
# SwiftUICharts
A description of this package.
Swift package for displaying charts effortlessly.
**First release of version 2.0 is coming soon! Also iOS 14 WidgetKit support is coming. I will update current charts and possibly extend with some new chart types to provide the best support for building informative and beautiful widgets for the new home screen 🥳 Stay tuned!**
![SwiftUI Charts](./Resources/showcase1.gif "SwiftUI Charts")
### Note:
**A version 2.0 is coming soon!!! 🎉🎉🎉**, so please hold off your PRs for a while. I'm writing a new code base with more sleek code architecture with an option for easier expansion. I'll make beta releases so you can test betas.
**If you'd like to contribute you can find tickets for the new version in the Issues under the `v2` tag, please read more at: [https://github.com/AppPear/ChartView/pull/89](https://github.com/AppPear/ChartView/pull/89)**
It supports:
* Line charts
* Bar charts
* Pie charts
### Slack
Join our Slack channel for day to day conversation and more insights:
[Slack invite link](https://join.slack.com/t/swiftuichartview/shared_invite/zt-el5pnmba-FvyraEsI~EwxqWHNfHZWZg)
### Installation:
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`
### Usage:
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:
### 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
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()
```
Adopts to dark mode automatically
![Line Charts](./Resources/showcase3.gif "Line Charts")
You can add your custom darkmode style by specifying:
```swift
let myCustomStyle = ChartStyle(...)
let myCutsomDarkModeStyle = ChartStyle(...)
myCustomStyle.darkModeStyle = myCutsomDarkModeStyle
```
**Line chart is interactive, so you can drag across to reveal the data points**
You can add a line chart with the following code:
```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
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

@@ -0,0 +1,44 @@
//
// ChartCell.swift
// ChartView
//
// Created by András Samu on 2019. 06. 12..
// Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI
public struct BarChartCell : View {
var value: Double
var index: Int = 0
var width: Float
var numberOfDataPoints: Int
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 {
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(self.touchLocation < 0 ? Double(self.index) * 0.04 : 0))
}
}
#if DEBUG
struct ChartCell_Previews : PreviewProvider {
static var previews: some View {
BarChartCell(value: Double(0.75), width: 320, numberOfDataPoints: 12, accentColor: Colors.OrangeStart, gradient: nil, touchLocation: .constant(-1))
}
}
#endif
@@ -0,0 +1,57 @@
//
// 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 {
guard let max = data.max() else {
return 1
}
return max != 0 ? max : 1
}
@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 {
Group {
BarChartRow(data: [0], accentColor: Colors.OrangeStart, touchLocation: .constant(-1))
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 @@
//
// ChartCell.swift
// ChartView
//
// Created by András Samu on 2019. 06. 12..
// Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI
public struct ChartCell : View {
var value: Double
var index: Int = 0
@State var scaleValue: Double = 0
public var body: some View {
Rectangle()
.frame(width: 6)
.cornerRadius(4)
.scaleEffect(CGSize(width: 1, height: self.scaleValue), anchor: .bottom)
.onAppear(){
self.scaleValue = self.value
}
.animation(Animation.spring().delay(Double(self.index) * 0.04))
}
}
#if DEBUG
struct ChartCell_Previews : PreviewProvider {
static var previews: some View {
ChartCell(value: Double(0.75))
}
}
#endif
-31
View File
@@ -1,31 +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 {
HStack(alignment: .bottom, spacing: 14){
ForEach(0..<data.count) { i in
ChartCell(value: Double(self.data[i])/Double(self.maxValue), index: i)
}
}.padding([.trailing,.leading])
}
}
#if DEBUG
struct ChartRow_Previews : PreviewProvider {
static var previews: some View {
ChartRow(data: [8,23,54,32,12,37,7])
}
}
#endif
-59
View File
@@ -1,59 +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)
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
public 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 extraLarge = 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 extraLarge = CGSize(width:360, height:240)
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)
}
}
+106
View File
@@ -0,0 +1,106 @@
//
// 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!
}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,152 @@
//
// 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()
}
}
}
var 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!
frame = CGSize(width: self.formSize.width, height: self.formSize.height/2)
self.dropShadow = dropShadow!
self.valueSpecifier = valueSpecifier!
self.rateValue = rateValue
}
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 != 0)
{
if (self.rateValue ?? 0 >= 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)
LineChartView(data: [282.502, 284.495, 283.51, 285.019, 285.197, 286.118, 288.737, 288.455, 289.391, 287.691, 285.878, 286.46, 286.252, 284.652, 284.129, 284.188], title: "Line chart", legend: "Basic")
.environment(\.colorScheme, .light)
}
}
}
@@ -0,0 +1,132 @@
//
// 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,
gradient: self.style.gradientColor
)
.offset(x: 30, y: -20)
.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 {
Group {
LineView(data: [8,23,54,32,12,37,7,23,43], title: "Full chart", style: Styles.lineChartStyleOne)
LineView(data: [282.502, 284.495, 283.51, 285.019, 285.197, 286.118, 288.737, 288.455, 289.391, 287.691, 285.878, 286.46, 286.252, 284.652, 284.129, 284.188], 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,166 @@
//
// 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
}
var 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? = nil,
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
frame = CGSize(width: self.formSize.width, height: self.formSize.height/2)
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)
}
if let rateValue = rateValue {
HStack {
if (rateValue >= 0){
Image(systemName: "arrow.up")
}else{
Image(systemName: "arrow.down")
}
Text("\(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] {
@@ -39,7 +39,10 @@ public struct PieChartRow : View {
#if DEBUG
struct PieChartRow_Previews : PreviewProvider {
static var previews: some View {
Group {
PieChartRow(data:[8,23,54,32,12,37,7,23,43], 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)
PieChartRow(data:[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)
}
}
}
@@ -0,0 +1,67 @@
//
// 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!
if self.formSize == ChartForm.large {
self.formSize = ChartForm.extraLarge
}
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