Skip to content

Commit

Permalink
Added Label,Value pairs so you can display a label for each point in …
Browse files Browse the repository at this point in the history
…Bar chart, added ability to change ecg image in the corner, added Generic number types to ChartData initialiser
  • Loading branch information
AppPear committed Jan 11, 2020
1 parent f7d9895 commit 1e362b9
Show file tree
Hide file tree
Showing 19 changed files with 213 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -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>
55 changes: 43 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Swift package for displaying charts effortlessly.

![SwiftUI Charts](./showcase1.gif "SwiftUI Charts")
![SwiftUI Charts](./Resources/showcase1.gif "SwiftUI Charts")

It supports:
* Line charts
Expand All @@ -29,15 +29,15 @@ Added an example project, with **iOS, watchOS** target: https://github.com/AppPe

**New full screen view called LineView!!!**

![Line Charts](./fullscreen2.gif "Line Charts")
![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](./showcase3.gif "Line Charts")
![Line Charts](./Resources/showcase3.gif "Line Charts")

**Line chart is interactive, so you can drag across to reveal the data points**

Expand All @@ -51,24 +51,55 @@ You can add a line chart with the following code:


## Bar charts
![Bar Charts](./showcase2.gif "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: [8,23,54,32,12,37,7,23,43], title: "Title", legend: "Legendary") // legend is optional
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: [8,23,54,32,12,37,7,23,43], title: "Title", form: ChartForm.small)
```
```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:
Expand Down Expand Up @@ -98,9 +129,9 @@ You can access built-in styles:
* barChartMidnightGreenLight
* barChartMidnightGreenDark

![Midnightgreen](./midnightgreen.gif "Midnightgreen")
![Midnightgreen](./Resources/midnightgreen.gif "Midnightgreen")

![Custom Charts](./showcase5.png "Custom Charts")
![Custom Charts](./Resources/showcase5.png "Custom Charts")


### You can customize the size of the chart with a ChartForm object:
Expand All @@ -117,10 +148,10 @@ BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", form: ChartForm.s

### WatchOS support for Bar charts:

![Pie Charts](./watchos1.png "Pie Charts")
![Pie Charts](./Resources/watchos1.png "Pie Charts")

## Pie charts
![Pie Charts](./showcase4.png "Pie Charts")
![Pie Charts](./Resources/showcase4.png "Pie Charts")

You can add a line chart with the following code:

Expand Down
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
Binary file added Resources/showcase2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
51 changes: 37 additions & 14 deletions Sources/SwiftUICharts/BarChart/BarChartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,37 @@
import SwiftUI

public struct BarChartView : View {
public var data: [Double]
private var data: ChartData
public var title: String
public var legend: String?
public var style: ChartStyle
public var formSize:CGSize
public var dropShadow: Bool

// let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
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) {
// selectionFeedbackGenerator.selectionChanged()
HapticFeedback.playSelection()
}
}
}
var isFullWidth:Bool {
return self.formSize == ChartForm.large
}
public init(data: [Double], title: String, legend: String? = nil, style: ChartStyle = Styles.barChartStyleOrangeLight, form: CGSize? = ChartForm.medium, dropShadow: Bool? = true){
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.formSize = form!
self.dropShadow = dropShadow!
self.cornerImage = cornerImage!
self.valueSpecifier = valueSpecifier!
}

public var body: some View {
Expand All @@ -53,7 +55,7 @@ public struct BarChartView : View {
.font(.headline)
.foregroundColor(self.style.textColor)
}else{
Text("\(self.currentValue)")
Text("\(self.currentValue, specifier: self.valueSpecifier)")
.font(.headline)
.foregroundColor(self.style.textColor)
}
Expand All @@ -65,16 +67,18 @@ public struct BarChartView : View {
.animation(.easeOut)
}
Spacer()
Image(systemName: "waveform.path.ecg")
self.cornerImage
.imageScale(.large)
.foregroundColor(self.style.legendTextColor)
}.padding()
BarChartRow(data: data, accentColor: self.style.accentColor, secondGradientAccentColor: self.style.secondGradientColor, touchLocation: self.$touchLocation)
if self.legend != nil && self.formSize == ChartForm.medium {
BarChartRow(data: data.points.map{$0.1}, accentColor: self.style.accentColor, secondGradientAccentColor: self.style.secondGradientColor, touchLocation: self.$touchLocation)
if self.legend != nil && self.formSize == ChartForm.medium && !self.showLabelValue{
Text(self.legend!)
.font(.headline)
.foregroundColor(self.style.legendTextColor)
.padding()
}else if (self.data.valuesGiven) {
LabelView(arrowOffset: self.getArrowOffset(touchLocation: self.touchLocation), title: .constant(self.getCurrentValue().0)).offset(x: self.getLabelViewOffset(touchLocation: self.touchLocation), y: -6)
}

}
Expand All @@ -83,27 +87,46 @@ public struct BarChartView : View {
.onChanged({ value in
self.touchLocation = value.location.x/self.formSize.width
self.showValue = true
self.currentValue = self.getCurrentValue()
self.currentValue = self.getCurrentValue().1
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 getCurrentValue()-> Double {
let index = max(0,min(self.data.count-1,Int(floor((self.touchLocation*self.formSize.width)/(self.formSize.width/CGFloat(self.data.count))))))
return self.data[index]
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) {
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: [8,23,54,32,12,37,7,23,43], title: "Title", legend: "Legendary")
BarChartView(data: TestData.values ,title: "Model 3 sales", legend: "Quarterly", valueSpecifier: "%.0f")
}
}
#endif
46 changes: 46 additions & 0 deletions Sources/SwiftUICharts/BarChart/LabelView.swift
Original file line number Diff line number Diff line change
@@ -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"))
}
}
40 changes: 33 additions & 7 deletions Sources/SwiftUICharts/Helpers.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// File.swift
//
//
//
// Created by András Samu on 2019. 07. 19..
//
Expand Down Expand Up @@ -128,17 +128,43 @@ public struct ChartStyle {
}
}

class ChartData: ObservableObject {
@Published var points: [Double] = [Double]()
@Published var currentPoint: Double? = nil
public class ChartData: ObservableObject {
@Published var points: [(String,Double)] = [(String,Double)]()
var valuesGiven: Bool = false
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
}

init(points:[Double]) {
self.points = points
public func onlyPoints() -> [Double] {
return self.points.map{ $0.1 }
}
}

class TestData{
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 {
Expand Down
11 changes: 7 additions & 4 deletions Sources/SwiftUICharts/LineChart/Legend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ struct Legend: View {
return frame.size.width / CGFloat(data.points.count-1)
}
var stepHeight: CGFloat {
if let min = data.points.min(), let max = data.points.max(), min != max {
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{
Expand All @@ -33,7 +34,8 @@ struct Legend: View {
}

var min: CGFloat {
return CGFloat(data.points.min() ?? 0)
let points = self.data.onlyPoints()
return CGFloat(points.min() ?? 0)
}

var body: some View {
Expand Down Expand Up @@ -80,8 +82,9 @@ struct Legend: View {
}

func getYLegend() -> [Double]? {
guard let max = data.points.max() else { return nil }
guard let min = data.points.min() else { return nil }
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]
}
Expand Down
Loading

0 comments on commit 1e362b9

Please sign in to comment.