├── .gitignore
├── .spi.yml
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── SwiftUICharts
│ ├── AxisView.swift
│ ├── BarChartView.swift
│ ├── BarsView.swift
│ ├── ChartGrid.swift
│ ├── Extensions
│ └── RandomAccessCollection.swift
│ ├── HorizontalBarChartView.swift
│ ├── LabelsView.swift
│ ├── LegendView.swift
│ ├── LineChartShape.swift
│ ├── LineChartView.swift
│ ├── Model
│ ├── ChartStyle.swift
│ └── DataPoint.swift
│ ├── Previews.swift
│ └── StackedHorizontalBarChartView.swift
├── Tests
├── LinuxMain.swift
└── SwiftUIChartsTests
│ ├── SwiftUIChartsTests.swift
│ └── XCTestManifests.swift
└── screenshots.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.spi.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 | builder:
3 | configs:
4 | - documentation_targets: [SwiftUICharts]
5 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Majid Jabrayilov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SwiftUICharts",
8 | platforms: [
9 | .macOS(.v11),
10 | .iOS(.v14),
11 | .watchOS(.v7),
12 | .tvOS(.v14)
13 | ],
14 | products: [
15 | // Products define the executables and libraries a package produces, and make them visible to other packages.
16 | .library(
17 | name: "SwiftUICharts",
18 | targets: ["SwiftUICharts"]),
19 | ],
20 | dependencies: [
21 | // Dependencies declare other packages that this package depends on.
22 | // .package(url: /* package url */, from: "1.0.0"),
23 | ],
24 | targets: [
25 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
26 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
27 | .target(
28 | name: "SwiftUICharts",
29 | dependencies: [])
30 | // .testTarget(
31 | // name: "SwiftUIChartsTests",
32 | // dependencies: ["SwiftUICharts"]),
33 | ]
34 | )
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftUICharts
2 | A simple line and bar charting library that support accessibility written using SwiftUI.
3 |
4 |
5 | ## Usage
6 |
7 | You can find all the examples [here](https://github.com/mecid/SwiftUICharts/blob/main/Sources/SwiftUICharts/Previews.swift).
8 |
9 | ### Vertical bar chart
10 | ```swift
11 | let highIntensity = Legend(color: .orange, label: "High Intensity", order: 5)
12 | let buildFitness = Legend(color: .yellow, label: "Build Fitness", order: 4)
13 | let fatBurning = Legend(color: .green, label: "Fat Burning", order: 3)
14 | let warmUp = Legend(color: .blue, label: "Warm Up", order: 2)
15 | let low = Legend(color: .gray, label: "Low", order: 1)
16 |
17 | let limit = DataPoint(value: 130, label: "5", legend: fatBurning)
18 |
19 | let points: [DataPoint] = [
20 | .init(value: 70, label: "1", legend: low),
21 | .init(value: 90, label: "2", legend: warmUp),
22 | .init(value: 91, label: "3", legend: warmUp),
23 | .init(value: 92, label: "4", legend: warmUp),
24 | .init(value: 130, label: "5", legend: fatBurning),
25 | .init(value: 124, label: "6", legend: fatBurning),
26 | .init(value: 135, label: "7", legend: fatBurning),
27 | .init(value: 133, label: "8", legend: fatBurning),
28 | .init(value: 136, label: "9", legend: fatBurning),
29 | .init(value: 138, label: "10", legend: fatBurning),
30 | .init(value: 150, label: "11", legend: buildFitness),
31 | .init(value: 151, label: "12", legend: buildFitness),
32 | .init(value: 150, label: "13", legend: buildFitness),
33 | .init(value: 136, label: "14", legend: fatBurning),
34 | .init(value: 135, label: "15", legend: fatBurning),
35 | .init(value: 130, label: "16", legend: fatBurning),
36 | .init(value: 130, label: "17", legend: fatBurning),
37 | .init(value: 150, label: "18", legend: buildFitness),
38 | .init(value: 151, label: "19", legend: buildFitness),
39 | .init(value: 150, label: "20", legend: buildFitness),
40 | .init(value: 160, label: "21", legend: highIntensity),
41 | .init(value: 159, label: "22", legend: highIntensity),
42 | .init(value: 161, label: "23", legend: highIntensity),
43 | .init(value: 158, label: "24", legend: highIntensity),
44 | ]
45 |
46 | BarChartView(dataPoints: points, limit: limit)
47 | ```
48 |
49 | ### Horizontal bar chart
50 | ```swift
51 | let warmUp = Legend(color: .blue, label: "Warm Up", order: 2)
52 | let low = Legend(color: .gray, label: "Low", order: 1)
53 |
54 | let points: [DataPoint] = [
55 | .init(value: 70, label: "1", legend: low),
56 | .init(value: 90, label: "2", legend: warmUp),
57 | .init(value: 91, label: "3", legend: warmUp),
58 | .init(value: 92, label: "4", legend: warmUp)
59 | ]
60 |
61 | HorizontalBarChartView(dataPoints: points)
62 | ```
63 |
64 | ### Line chart
65 | ```swift
66 | let buildFitness = Legend(color: .yellow, label: "Build Fitness", order: 4)
67 | let fatBurning = Legend(color: .green, label: "Fat Burning", order: 3)
68 | let warmUp = Legend(color: .blue, label: "Warm Up", order: 2)
69 | let low = Legend(color: .gray, label: "Low", order: 1)
70 |
71 | let points: [DataPoint] = [
72 | .init(value: 70, label: "1", legend: low),
73 | .init(value: 90, label: "2", legend: warmUp),
74 | .init(value: 91, label: "3", legend: warmUp),
75 | .init(value: 92, label: "4", legend: warmUp),
76 | .init(value: 130, label: "5", legend: fatBurning),
77 | .init(value: 124, label: "6", legend: fatBurning),
78 | .init(value: 135, label: "7", legend: fatBurning),
79 | .init(value: 133, label: "8", legend: fatBurning),
80 | .init(value: 136, label: "9", legend: fatBurning),
81 | .init(value: 138, label: "10", legend: fatBurning),
82 | .init(value: 150, label: "11", legend: buildFitness),
83 | .init(value: 151, label: "12", legend: buildFitness),
84 | .init(value: 150, label: "13", legend: buildFitness)
85 | ]
86 |
87 | LineChartView(dataPoints: points)
88 | ```
89 |
90 | ## Installation
91 | Add this Swift package in Xcode using its Github repository url. (File > Swift Packages > Add Package Dependency...)
92 |
93 | ## Author
94 | Majid Jabrayilov: cmecid@gmail.com
95 |
96 | ## License
97 | SwiftUICharts is available under the MIT license. See the LICENSE file for more info.
98 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/AxisView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AxisView.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 6/27/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | struct AxisView: View {
11 | let dataPoints: [DataPoint]
12 |
13 | var body: some View {
14 | VStack {
15 | dataPoints.max().map {
16 | Text(String(Int($0.endValue)))
17 | .foregroundColor(.accentColor)
18 | .font(.caption)
19 | }
20 | Spacer()
21 | dataPoints.max().map {
22 | Text(String(Int($0.endValue / 2)))
23 | .foregroundColor(.accentColor)
24 | .font(.caption)
25 | }
26 | Spacer()
27 | }
28 | }
29 | }
30 |
31 | #if DEBUG
32 | struct AxisView_Previews: PreviewProvider {
33 | static var previews: some View {
34 | AxisView(dataPoints: DataPoint.mock)
35 | }
36 | }
37 | #endif
38 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/BarChartView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BarChartView.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 6/21/19.
6 | // Copyright © 2019 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | /// Type that defines a bar chart style.
11 | public struct BarChartStyle: ChartStyle {
12 | /// Minimal height for a bar chart view
13 | public let barMinHeight: CGFloat
14 | /// Boolean value indicating whenever show chart axis
15 | public let showAxis: Bool
16 | /// Leading padding for the value axis displayed in the chart
17 | public let axisLeadingPadding: CGFloat
18 | /// Boolean value indicating whenever show chart labels
19 | public let showLabels: Bool
20 | /// The count of labels that should be shown below the chart. Nil value shows all the labels.
21 | public let labelCount: Int?
22 | public let showLegends: Bool
23 | /**
24 | Creates new bar chart style with the following parameters.
25 |
26 | - Parameters:
27 | - barMinHeight: The minimal height for the bar that presents the biggest value. Default is 100.
28 | - showAxis: Bool value that controls whenever to show axis.
29 | - axisLeadingPadding: Leading padding for axis line. Default is 0.
30 | - showLabels: Bool value that controls whenever to show labels.
31 | - labelCount: The count of labels that should be shown below the chart. Default is all.
32 | - showLegends: Bool value that controls whenever to show legends.
33 | */
34 | #if os(watchOS)
35 | public init(
36 | barMinHeight: CGFloat = 50,
37 | showAxis: Bool = true,
38 | axisLeadingPadding: CGFloat = 0,
39 | showLabels: Bool = true,
40 | labelCount: Int? = nil,
41 | showLegends: Bool = true
42 | ) {
43 | self.barMinHeight = barMinHeight
44 | self.showAxis = showAxis
45 | self.axisLeadingPadding = axisLeadingPadding
46 | self.showLabels = showLabels
47 | self.labelCount = labelCount
48 | self.showLegends = showLegends
49 | }
50 | #else
51 | public init(
52 | barMinHeight: CGFloat = 100,
53 | showAxis: Bool = true,
54 | axisLeadingPadding: CGFloat = 0,
55 | showLabels: Bool = true,
56 | labelCount: Int? = nil,
57 | showLegends: Bool = true
58 | ) {
59 | self.barMinHeight = barMinHeight
60 | self.showAxis = showAxis
61 | self.axisLeadingPadding = axisLeadingPadding
62 | self.showLabels = showLabels
63 | self.labelCount = labelCount
64 | self.showLegends = showLegends
65 | }
66 | #endif
67 | }
68 |
69 | /// SwiftUI view that draws bars by placing them into a horizontal container.
70 | public struct BarChartView: View {
71 | @Environment(\.chartStyle) var chartStyle
72 |
73 | let dataPoints: [DataPoint]
74 | let limit: DataPoint?
75 |
76 | /**
77 | Creates new bar chart view with the following parameters.
78 |
79 | - Parameters:
80 | - dataPoints: The array of data points that will be used to draw the bar chart.
81 | - limit: The horizontal line that will be drawn over bars. Default is nil.
82 | */
83 | public init(dataPoints: [DataPoint], limit: DataPoint? = nil) {
84 | // insert additional invisible data point to guarantee spacing
85 | self.dataPoints = dataPoints + [
86 | .init(
87 | value: (dataPoints.max()?.endValue ?? 0) * 1.2,
88 | label: "invisible",
89 | legend: .init(
90 | color: .clear,
91 | label: "clear"
92 | ),
93 | visible: false
94 | )
95 | ]
96 |
97 | self.limit = limit
98 | }
99 |
100 | private var style: BarChartStyle {
101 | (chartStyle as? BarChartStyle) ?? .init()
102 | }
103 |
104 | private var grid: some View {
105 | ChartGrid()
106 | .stroke(
107 | style.showAxis ? Color.accentColor : .clear,
108 | style: StrokeStyle(
109 | lineWidth: 1,
110 | lineCap: .round,
111 | lineJoin: .round,
112 | miterLimit: 0,
113 | dash: [1, 8],
114 | dashPhase: 0
115 | )
116 | )
117 | }
118 |
119 | public var body: some View {
120 | VStack {
121 | HStack(spacing: 0) {
122 | VStack {
123 | BarsView(dataPoints: dataPoints, limit: limit, showAxis: style.showAxis)
124 | .frame(minHeight: style.barMinHeight)
125 | .background(grid)
126 |
127 | if style.showLabels {
128 | LabelsView(
129 | dataPoints: dataPoints,
130 | labelCount: style.labelCount ?? dataPoints.count
131 | ).accessibilityHidden(true)
132 | }
133 | }
134 | if style.showAxis {
135 | AxisView(dataPoints: dataPoints)
136 | .fixedSize(horizontal: true, vertical: false)
137 | .accessibilityHidden(true)
138 | .padding(.leading, style.axisLeadingPadding)
139 | }
140 | }
141 |
142 | if style.showLegends {
143 | LegendView(dataPoints: limit.map { [$0] + dataPoints} ?? dataPoints)
144 | .padding()
145 | .accessibilityHidden(true)
146 | }
147 | }
148 | }
149 | }
150 |
151 | #if DEBUG
152 | struct BarChartView_Previews : PreviewProvider {
153 | static var previews: some View {
154 | let limit = Legend(color: .purple, label: "Trend")
155 | let limitBar = DataPoint(value: 100, label: "Trend", legend: limit)
156 | return HStack(spacing: 0) {
157 | BarChartView(dataPoints: DataPoint.mock, limit: limitBar)
158 | BarChartView(dataPoints: DataPoint.mock, limit: limitBar)
159 | }.chartStyle(BarChartStyle(showLabels: false, showLegends: false))
160 | }
161 | }
162 | #endif
163 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/BarsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BarsView.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 6/28/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | struct BarsView: View {
11 | let dataPoints: [DataPoint]
12 | let limit: DataPoint?
13 | let showAxis: Bool
14 |
15 | @Environment(\.sizeCategory) private var sizeCategory
16 |
17 | private enum Size {
18 | static let cornerRadius: CGFloat = 4
19 | static let limitHeight: CGFloat = 4
20 |
21 | static func spacing(for count: Int) -> CGFloat {
22 | return count > 40 ? 0 : 2
23 | }
24 |
25 | static func limitMin(for sizeCategory: ContentSizeCategory) -> CGFloat {
26 | return sizeCategory.isAccessibilityCategory ? 0.4 : 0.2
27 | }
28 | }
29 |
30 | private var max: CGFloat {
31 | var allDataPoints = dataPoints
32 |
33 | if let limit = limit {
34 | allDataPoints.append(limit)
35 | }
36 |
37 | return allDataPoints.max()
38 | .map { $0.endValue == 0 ? 1: $0.endValue } ?? 1
39 | }
40 |
41 | var body: some View {
42 | GeometryReader { geometry in
43 | ZStack(alignment: .bottomTrailing) {
44 | HStack(alignment: .bottom, spacing: Size.spacing(for: dataPoints.count)) {
45 | ForEach(dataPoints.filter(\.visible)) { bar in
46 | barView(for: bar, in: geometry)
47 | }
48 | }
49 | .frame(minHeight: 0, maxHeight: .infinity, alignment: .bottomLeading)
50 |
51 | limit.map { limit in
52 | limitView(for: limit, in: geometry)
53 | }
54 | }
55 | }
56 | }
57 |
58 | private func barView(for point: DataPoint, in geometry: GeometryProxy) -> some View {
59 | Capsule(style: .continuous)
60 | .fill(point.legend.color)
61 | .accessibilityLabel(Text(point.label))
62 | .accessibilityValue(Text(point.legend.label))
63 | .offset(y: -point.startValue / max * geometry.size.height)
64 | .frame(height: (point.endValue-point.startValue) / max * geometry.size.height)
65 | }
66 |
67 | private func limitView(for limit: DataPoint, in geometry: GeometryProxy) -> some View {
68 | ZStack {
69 | RoundedRectangle(cornerRadius: Size.cornerRadius, style: .continuous)
70 | .frame(height: Size.limitHeight)
71 | .foregroundColor(limit.legend.color)
72 | Text(limit.label)
73 | .padding(.horizontal)
74 | .foregroundColor(.white)
75 | .background(limit.legend.color)
76 | .clipShape(RoundedRectangle(cornerRadius: Size.cornerRadius, style: .continuous))
77 | }
78 | .position(
79 | x: geometry.size.width / 2,
80 | y: geometry.size.height - Swift.max(
81 | (limit.endValue / max * geometry.size.height),
82 | geometry.size.height * Size.limitMin(for: sizeCategory)
83 | )
84 | )
85 | }
86 | }
87 |
88 | #if DEBUG
89 | struct BarsView_Previews: PreviewProvider {
90 | static var previews: some View {
91 | BarsView(dataPoints: DataPoint.mock, limit: nil, showAxis: true)
92 | }
93 | }
94 | #endif
95 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/ChartGrid.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChartGrid.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 7/4/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ChartGrid: Shape {
12 | func path(in rect: CGRect) -> Path {
13 | Path { path in
14 | path.move(to: CGPoint(x: 0, y: 0))
15 | path.addLine(to: CGPoint(x: rect.width, y: 0))
16 |
17 | path.move(to: CGPoint(x: 0, y: rect.height))
18 | path.addLine(to: CGPoint(x: rect.width, y: rect.height))
19 |
20 | path.move(to: CGPoint(x: 0, y: rect.height / 2))
21 | path.addLine(to: CGPoint(x: rect.width, y: rect.height / 2))
22 | }
23 | }
24 | }
25 |
26 | #if DEBUG
27 | struct BarChartGrid_Previews: PreviewProvider {
28 | static var previews: some View {
29 | ChartGrid()
30 | .stroke(
31 | style: StrokeStyle(
32 | lineWidth: 1,
33 | lineCap: .round,
34 | lineJoin: .round,
35 | miterLimit: 0,
36 | dash: [1, 8],
37 | dashPhase: 0
38 | )
39 | )
40 | }
41 | }
42 | #endif
43 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/Extensions/RandomAccessCollection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Majid Jabrayilov on 20.07.20.
6 | //
7 | import Foundation
8 |
9 | extension RandomAccessCollection {
10 | func indexed() -> Array<(offset: Int, element: Element)> {
11 | Array(enumerated())
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/HorizontalBarChartView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HorizontalBarChartView.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 5/12/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | /// SwiftUI view that draws bars by placing them into a vertical container.
11 | public struct HorizontalBarChartView: View {
12 | let dataPoints: [DataPoint]
13 | let barMaxWidth: CGFloat
14 |
15 | /**
16 | Creates new horizontal bar chart with the following parameters.
17 |
18 | - Parameters:
19 | - dataPoints: The array of data points that will be used to draw the bar chart.
20 | - barMaxWidth: The maximal width for the bar that presents the biggest value. Default is 100.
21 | */
22 | public init(dataPoints: [DataPoint], barMaxWidth: CGFloat = 100) {
23 | self.dataPoints = dataPoints
24 | self.barMaxWidth = barMaxWidth
25 | }
26 |
27 | private var max: Double {
28 | guard let max = dataPoints.max()?.endValue, max != 0 else {
29 | return 1
30 | }
31 | return max
32 | }
33 |
34 | public var body: some View {
35 | VStack(alignment: .leading, spacing: 8) {
36 | ForEach(dataPoints) { bar in
37 | #if os(watchOS)
38 | VStack(alignment: .leading) {
39 | RoundedRectangle(cornerRadius: 8, style: .continuous)
40 | .foregroundColor(bar.legend.color)
41 | .frame(width: CGFloat(bar.endValue / self.max) * barMaxWidth, height: 16)
42 | HStack {
43 | Circle()
44 | .foregroundColor(bar.legend.color)
45 | .frame(width: 8, height: 8)
46 |
47 | Text(bar.legend.label) + Text(", ") + Text(bar.label)
48 |
49 | // TODO: temp fix
50 | Spacer()
51 | }
52 | }
53 | #else
54 | HStack {
55 | RoundedRectangle(cornerRadius: 8, style: .continuous)
56 | .foregroundColor(bar.legend.color)
57 | .frame(width: CGFloat(bar.endValue / self.max) * barMaxWidth, height: 16)
58 |
59 | Circle()
60 | .foregroundColor(bar.legend.color)
61 | .frame(width: 8, height: 8)
62 |
63 | Text(bar.legend.label) + Text(", ") + Text(bar.label)
64 |
65 | // TODO: temp fix
66 | Spacer()
67 | }
68 | #endif
69 | }
70 | }
71 | }
72 | }
73 |
74 | struct HorizontalBarChart_Previews: PreviewProvider {
75 | static var previews: some View {
76 | let veryLow = Legend(color: .black, label: "Very Low")
77 | let low = Legend(color: .gray, label: "Low")
78 | let resting = Legend(color: .blue, label: "Resting")
79 | let highResting = Legend(color: .orange, label: "High Resting")
80 | let elevated = Legend(color: .red, label: "Elevated")
81 |
82 | let dataPoints: [DataPoint] = [
83 | DataPoint(value: 0.1, label: "10%", legend: veryLow),
84 | DataPoint(value: 0.15, label: "15%", legend: low),
85 | DataPoint(value: 0.60, label: "60%", legend: resting),
86 | DataPoint(value: 0.1, label: "10%", legend: highResting),
87 | DataPoint(value: 0.05, label: "5%", legend: elevated)
88 | ]
89 |
90 | return HorizontalBarChartView(dataPoints: dataPoints)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/LabelsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LabelsView.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 6/27/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | struct LabelsView: View {
11 | let dataPoints: [DataPoint]
12 | let labelCount: Int
13 |
14 | private var threshold: Int {
15 | let threshold = Double(dataPoints.count) / Double(labelCount)
16 | return Int(threshold.rounded(.awayFromZero))
17 | }
18 |
19 | var body: some View {
20 | HStack(spacing: 0) {
21 | ForEach(dataPoints.filter(\.visible).indexed(), id: \.1.id) { index, bar in
22 | if index % self.threshold == 0 {
23 | Text(bar.label)
24 | .multilineTextAlignment(.center)
25 | .foregroundColor(.accentColor)
26 | .font(.caption)
27 | Spacer()
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
34 | #if DEBUG
35 | struct LabelsView_Previews: PreviewProvider {
36 | static var previews: some View {
37 | LabelsView(dataPoints: DataPoint.mock, labelCount: 3)
38 | }
39 | }
40 | #endif
41 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/LegendView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LegendView.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 6/27/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | struct LegendView: View {
11 | let legends: [Legend]
12 |
13 | init(dataPoints: [DataPoint]) {
14 | legends = Array(Set(dataPoints.filter(\.visible).map { $0.legend })).sorted()
15 | }
16 |
17 | var body: some View {
18 | LazyVGrid(columns: [.init(.adaptive(minimum: 100))], alignment: .leading) {
19 | ForEach(legends, id: \.color) { legend in
20 | HStack(alignment: .center) {
21 | Circle()
22 | .fill(legend.color)
23 | .frame(width: 16, height: 16)
24 |
25 | Text(legend.label)
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
32 | #if DEBUG
33 | struct LegendView_Previews: PreviewProvider {
34 | static var previews: some View {
35 | LegendView(dataPoints: DataPoint.mock)
36 | }
37 | }
38 | #endif
39 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/LineChartShape.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LineChartShape.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 24.09.20.
6 | //
7 | import SwiftUI
8 |
9 | struct LineChartShape: Shape {
10 | let dataPoints: [DataPoint]
11 | var closePath: Bool = true
12 |
13 | func path(in rect: CGRect) -> Path {
14 | Path { path in
15 | let start = CGFloat(dataPoints.first?.endValue ?? 0) / CGFloat(dataPoints.max()?.endValue ?? 1)
16 | path.move(to: CGPoint(x: 0, y: rect.height - rect.height * start))
17 | let stepX = rect.width / CGFloat(dataPoints.count)
18 | var currentX: CGFloat = 0
19 | dataPoints.forEach {
20 | currentX += stepX
21 | let y = CGFloat($0.endValue / (dataPoints.max()?.endValue ?? 1)) * rect.height
22 | path.addLine(to: CGPoint(x: currentX, y: rect.height - y))
23 | }
24 |
25 | if closePath {
26 | path.addLine(to: CGPoint(x: currentX, y: rect.height))
27 | path.addLine(to: CGPoint(x: 0, y: rect.height))
28 | path.closeSubpath()
29 | }
30 | }
31 | }
32 | }
33 |
34 | #if DEBUG
35 | struct LineChartShape_Previews: PreviewProvider {
36 | static var previews: some View {
37 | LineChartShape(dataPoints: DataPoint.mock, closePath: true)
38 | }
39 | }
40 | #endif
41 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/LineChartView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LineChartView.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 6/27/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | /// Type that defines a line chart style.
11 | public struct LineChartStyle: ChartStyle {
12 | /// Type that defines the style of line drawing.
13 | public enum Drawing {
14 | case fill
15 | case stroke(width: CGFloat = 1)
16 | }
17 |
18 | /// Minimal height for a line chart view.
19 | public let lineMinHeight: CGFloat
20 | /// Boolean value indicating whenever show chart axis.
21 | public let showAxis: Bool
22 | /// Leading padding for the value axis displayed in the chart.
23 | public let axisLeadingPadding: CGFloat
24 | /// Boolean value indicating whenever show chart labels.
25 | public let showLabels: Bool
26 | /// The count of labels that should be shown below the chart. Nil value shows all the labels.
27 | public let labelCount: Int?
28 | public let showLegends: Bool
29 |
30 | /// Value that controls type of drawing.
31 | public let drawing: Drawing
32 |
33 | /**
34 | Creates new line chart style with the following parameters.
35 |
36 | - Parameters:
37 | - lineMinHeight: The minimal height for the point that presents the biggest value. Default is 100.
38 | - showAxis: Bool value that controls whenever to show axis.
39 | - axisLeadingPadding: Leading padding for axis line. Default is 0.
40 | - showLabels: Bool value that controls whenever to show labels.
41 | - labelCount: The count of labels that should be shown below the the chart. Default is all.
42 | - showLegends: Bool value that controls whenever to show legends.
43 | - drawing: Value that controls type of drawing. Default is fill.
44 | */
45 | #if os(watchOS)
46 | public init(
47 | lineMinHeight: CGFloat = 50,
48 | showAxis: Bool = true,
49 | axisLeadingPadding: CGFloat = 0,
50 | showLabels: Bool = true,
51 | labelCount: Int? = nil,
52 | showLegends: Bool = true,
53 | drawing: Drawing = .fill
54 | ) {
55 | self.lineMinHeight = lineMinHeight
56 | self.showAxis = showAxis
57 | self.axisLeadingPadding = axisLeadingPadding
58 | self.showLabels = showLabels
59 | self.labelCount = labelCount
60 | self.showLegends = showLegends
61 | self.drawing = drawing
62 | }
63 | #else
64 | public init(
65 | lineMinHeight: CGFloat = 100,
66 | showAxis: Bool = true,
67 | axisLeadingPadding: CGFloat = 0,
68 | showLabels: Bool = true,
69 | labelCount: Int? = nil,
70 | showLegends: Bool = true,
71 | drawing: Drawing = .fill
72 | ) {
73 | self.lineMinHeight = lineMinHeight
74 | self.showAxis = showAxis
75 | self.axisLeadingPadding = axisLeadingPadding
76 | self.showLabels = showLabels
77 | self.labelCount = labelCount
78 | self.showLegends = showLegends
79 | self.drawing = drawing
80 | }
81 | #endif
82 | }
83 |
84 | /// SwiftUI view that draws data points by drawing a line.
85 | public struct LineChartView: View {
86 | @Environment(\.chartStyle) var chartStyle
87 | let dataPoints: [DataPoint]
88 |
89 | /**
90 | Creates new line chart view with the following parameters.
91 |
92 | - Parameters:
93 | - dataPoints: The array of data points that will be used to draw the bar chart.
94 | */
95 | public init(dataPoints: [DataPoint]) {
96 | self.dataPoints = dataPoints
97 | }
98 |
99 | private var style: LineChartStyle {
100 | (chartStyle as? LineChartStyle) ?? .init()
101 | }
102 |
103 | private var gradient: LinearGradient {
104 | let colors = dataPoints.map(\.legend).map(\.color)
105 | return LinearGradient(
106 | gradient: Gradient(colors: colors),
107 | startPoint: .leading,
108 | endPoint: .trailing
109 | )
110 | }
111 |
112 | private var grid: some View {
113 | ChartGrid()
114 | .stroke(
115 | style.showAxis ? Color.secondary : .clear,
116 | style: StrokeStyle(
117 | lineWidth: 1,
118 | lineCap: .round,
119 | lineJoin: .round,
120 | miterLimit: 0,
121 | dash: [1, 8],
122 | dashPhase: 1
123 | )
124 | )
125 | }
126 |
127 | public var body: some View {
128 | VStack {
129 | HStack(spacing: 0) {
130 | if case let LineChartStyle.Drawing.stroke(width) = style.drawing {
131 | LineChartShape(dataPoints: dataPoints, closePath: false)
132 | .stroke(gradient, style: .init(lineWidth: width))
133 | .frame(minHeight: style.lineMinHeight)
134 | .background(grid)
135 | } else {
136 | LineChartShape(dataPoints: dataPoints, closePath: true)
137 | .fill(gradient)
138 | .frame(minHeight: style.lineMinHeight)
139 | .background(grid)
140 | }
141 |
142 | if style.showAxis {
143 | AxisView(dataPoints: dataPoints)
144 | .accessibilityHidden(true)
145 | .padding(.leading, style.axisLeadingPadding)
146 | }
147 | }
148 |
149 | if style.showLabels {
150 | LabelsView(dataPoints: dataPoints, labelCount: style.labelCount ?? dataPoints.count)
151 | .accessibilityHidden(true)
152 | }
153 |
154 | if style.showLegends {
155 | LegendView(dataPoints: dataPoints)
156 | .padding()
157 | .accessibilityHidden(true)
158 | }
159 | }
160 | }
161 | }
162 |
163 | #if DEBUG
164 | struct LineChartView_Previews: PreviewProvider {
165 | static var previews: some View {
166 | HStack {
167 | LineChartView(dataPoints: DataPoint.mock)
168 | LineChartView(dataPoints: DataPoint.mock)
169 | }.chartStyle(LineChartStyle(showAxis: false, showLabels: false))
170 | }
171 | }
172 | #endif
173 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/Model/ChartStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChartStyle.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 08.12.20.
6 | //
7 | import SwiftUI
8 |
9 | /// Protocol type that defines general chart styling options
10 | public protocol ChartStyle {
11 | /// Boolean value indicating whenever show chart legends
12 | var showLegends: Bool { get }
13 | }
14 |
15 | struct ChartStyleEnvironmentKey: EnvironmentKey {
16 | static var defaultValue: ChartStyle?
17 | }
18 |
19 | extension EnvironmentValues {
20 | var chartStyle: ChartStyle? {
21 | get { self[ChartStyleEnvironmentKey.self] }
22 | set { self[ChartStyleEnvironmentKey.self] = newValue }
23 | }
24 | }
25 |
26 | extension View {
27 | public func chartStyle(_ style: ChartStyle) -> some View {
28 | environment(\.chartStyle, style)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/Model/DataPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataPoint.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 5/13/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | /// The type that describes the group of data points in the chart.
11 | public struct Legend {
12 | /// Color representing the legend
13 | let color: Color
14 |
15 | /// Localized string key representing the legend
16 | let label: LocalizedStringKey
17 |
18 | /// Integer representing the value to sort the array of legends
19 | let order: Int
20 |
21 | /**
22 | Creates new legend with the following parameters.
23 |
24 | - Parameters:
25 | - color: The color of the group that will be used to draw data points.
26 | - label: LocalizedStringKey that describes the legend.
27 | - order: The number that will be used to sort chart legends list. Default value is 0.
28 | */
29 | public init(color: Color, label: LocalizedStringKey, order: Int = 0) {
30 | self.color = color
31 | self.label = label
32 | self.order = order
33 | }
34 | }
35 |
36 | extension Legend: Comparable {
37 | public static func < (lhs: Self, rhs: Self) -> Bool {
38 | lhs.order < rhs.order
39 | }
40 | }
41 |
42 | extension Legend: Hashable {
43 | public func hash(into hasher: inout Hasher) {
44 | hasher.combine(color)
45 | }
46 | }
47 |
48 | /// The type that describes a data point in the chart.
49 | public struct DataPoint: Identifiable {
50 | /// Random unique identifier
51 | public let id = UUID()
52 | /// Starting point of a bar (used only in the ``BarChartView``)
53 | public let startValue: Double
54 |
55 | /// Double value representing the data point
56 | public let endValue: Double
57 |
58 | /// LocalizedStringKey representing the data point
59 | public let label: LocalizedStringKey
60 |
61 | /// ``Legend`` value representing the data point
62 | public let legend: Legend
63 |
64 | /// Swift.Bool value controlling the visibility of the data point in the chart
65 | public let visible: Bool
66 |
67 | /**
68 | Creates new data point with the following parameters.
69 |
70 | - Parameters:
71 | - value: Double that represents a value of the point in the chart.
72 | - label: LocalizedStringKey that describes the point.
73 | - legend: The legend of data point, usually appears below the chart.
74 | - visible: The boolean that controls the visibility of the data point in the chart. Default value is true.
75 | */
76 | public init(value: Double, label: LocalizedStringKey, legend: Legend, visible: Bool = true) {
77 | self.startValue = 0
78 | self.endValue = value
79 | self.label = label
80 | self.legend = legend
81 | self.visible = visible
82 | }
83 |
84 | /**
85 | Creates new data point with the following parameters.
86 |
87 | - Parameters:
88 | - startValue: Double that represents a start value of the point in the chart.
89 | - endValue: Double that represents an end value of the point in the chart.
90 | - label: LocalizedStringKey that describes the point.
91 | - legend: The legend of data point, usually appears below the chart.
92 | - visible: The boolean that controls the visibility of the data point in the chart. Default value is true.
93 | */
94 | public init(
95 | startValue: Double,
96 | endValue: Double,
97 | label: LocalizedStringKey,
98 | legend: Legend,
99 | visible: Bool = true
100 | ) {
101 | self.startValue = startValue
102 | self.endValue = endValue
103 | self.label = label
104 | self.legend = legend
105 | self.visible = visible
106 | }
107 |
108 | }
109 |
110 | extension DataPoint: Comparable {
111 | public static func < (lhs: DataPoint, rhs: DataPoint) -> Bool {
112 | lhs.endValue < rhs.endValue
113 | }
114 | }
115 |
116 | #if DEBUG
117 | extension DataPoint {
118 | static var mock: [DataPoint] {
119 | let highIntensity = Legend(color: .orange, label: "High Intensity", order: 5)
120 | let buildFitness = Legend(color: .yellow, label: "Build Fitness", order: 4)
121 | let fatBurning = Legend(color: .green, label: "Fat Burning", order: 3)
122 | let warmUp = Legend(color: .blue, label: "Warm Up", order: 2)
123 | let low = Legend(color: .gray, label: "Low", order: 1)
124 |
125 | return [
126 | .init(value: 70, label: "1", legend: low),
127 | .init(value: 90, label: "2", legend: warmUp),
128 | .init(value: 91, label: "3", legend: warmUp),
129 | .init(value: 92, label: "4", legend: warmUp),
130 | .init(value: 130, label: "5", legend: fatBurning),
131 | .init(value: 124, label: "6", legend: fatBurning),
132 | .init(value: 135, label: "7", legend: fatBurning),
133 | .init(value: 133, label: "8", legend: fatBurning),
134 | .init(value: 136, label: "9", legend: fatBurning),
135 | .init(value: 138, label: "10", legend: fatBurning),
136 | .init(value: 150, label: "11", legend: buildFitness),
137 | .init(value: 151, label: "12", legend: buildFitness),
138 | .init(value: 150, label: "13", legend: buildFitness),
139 | .init(value: 136, label: "14", legend: fatBurning),
140 | .init(value: 135, label: "15", legend: fatBurning),
141 | .init(value: 130, label: "16", legend: fatBurning),
142 | .init(value: 130, label: "17", legend: fatBurning),
143 | .init(value: 150, label: "18", legend: buildFitness),
144 | .init(value: 151, label: "19", legend: buildFitness),
145 | .init(value: 150, label: "20", legend: buildFitness),
146 | .init(value: 160, label: "21", legend: highIntensity),
147 | .init(value: 159, label: "22", legend: highIntensity),
148 | .init(value: 161, label: "23", legend: highIntensity),
149 | .init(value: 158, label: "24", legend: highIntensity),
150 | ]
151 | }
152 | }
153 | #endif
154 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/Previews.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Previews.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 31.05.22.
6 | //
7 | import SwiftUI
8 |
9 | #if DEBUG
10 | struct Previews: PreviewProvider {
11 | static var previews: some View {
12 | BarChartView(dataPoints: DataPoint.mock)
13 | .chartStyle(
14 | BarChartStyle(
15 | barMinHeight: 100,
16 | showAxis: true,
17 | axisLeadingPadding: 0,
18 | showLabels: true,
19 | labelCount: 5,
20 | showLegends: true
21 | )
22 | )
23 |
24 | BarChartView(
25 | dataPoints: DataPoint.mock.map {
26 | DataPoint(
27 | startValue: Double.random(in: 0..<$0.endValue),
28 | endValue: $0.endValue,
29 | label: $0.label,
30 | legend: $0.legend
31 | )
32 | }
33 | )
34 | .chartStyle(BarChartStyle(showLabels: false))
35 |
36 | HStack {
37 | BarChartView(
38 | dataPoints: Array(DataPoint.mock[0...10]),
39 | limit: DataPoint.mock[0]
40 | )
41 | BarChartView(
42 | dataPoints: Array(DataPoint.mock[11...20]),
43 | limit: DataPoint.mock[1]
44 | )
45 | }
46 | .chartStyle(BarChartStyle(showAxis: false, showLabels: false, showLegends: false))
47 |
48 | BarChartView(dataPoints: DataPoint.mock, limit: DataPoint.mock[3])
49 | .chartStyle(
50 | BarChartStyle(
51 | barMinHeight: 100,
52 | showAxis: true,
53 | axisLeadingPadding: 0,
54 | showLabels: true,
55 | labelCount: 5,
56 | showLegends: true
57 | )
58 | )
59 |
60 | StackedHorizontalBarChartView(
61 | dataPoints: Array(DataPoint.mock[0...7])
62 | )
63 | .frame(maxHeight: 80)
64 | .padding()
65 | .chartStyle(
66 | StackedHorizontalBarChartStyle(
67 | showLegends: true,
68 | cornerRadius: 8,
69 | spacing: 0
70 | )
71 | )
72 |
73 | HorizontalBarChartView(
74 | dataPoints: DataPoint.mock,
75 | barMaxWidth: 200
76 | )
77 |
78 | LineChartView(dataPoints: DataPoint.mock)
79 | .chartStyle(
80 | LineChartStyle(
81 | lineMinHeight: 100,
82 | showAxis: true,
83 | axisLeadingPadding: 0,
84 | showLabels: true,
85 | labelCount: 10,
86 | showLegends: true,
87 | drawing: .fill
88 | )
89 | )
90 |
91 | LineChartView(dataPoints: DataPoint.mock)
92 | .chartStyle(
93 | LineChartStyle(
94 | lineMinHeight: 100,
95 | showAxis: true,
96 | axisLeadingPadding: 0,
97 | showLabels: true,
98 | labelCount: 10,
99 | showLegends: true,
100 | drawing: .stroke(width: 4)
101 | )
102 | )
103 |
104 | }
105 | }
106 | #endif
107 |
--------------------------------------------------------------------------------
/Sources/SwiftUICharts/StackedHorizontalBarChartView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StackedHorizontalBarChartView.swift
3 | // SwiftUICharts
4 | //
5 | // Created by Majid Jabrayilov on 05.05.22.
6 | //
7 | import SwiftUI
8 |
9 | /// Type that defines horizontally stacked bar chart style.
10 | public struct StackedHorizontalBarChartStyle: ChartStyle {
11 | /// Boolean value indicating whenever to show chart legend
12 | public let showLegends: Bool
13 | /// Spacing between stacked horizontal bars
14 | public let spacing: CGFloat
15 | /// Corner radius for the whole chart view
16 | public let cornerRadius: CGFloat
17 |
18 | /**
19 | Creates a new stacked bar chart style with the following parameters.
20 |
21 | - Parameters:
22 | - showLegends: Bool value that controls whenever to show the legend.
23 | - cornerRadius: Corner radius used to round chart corners.
24 | - spacing: Spacing between stacked bars in the chart.
25 | */
26 | public init(
27 | showLegends: Bool = true,
28 | cornerRadius: CGFloat = 8,
29 | spacing: CGFloat = 0
30 | ) {
31 | self.showLegends = showLegends
32 | self.cornerRadius = cornerRadius
33 | self.spacing = spacing
34 | }
35 | }
36 |
37 | /// SwiftUI view that draws data points as horizontally stacked bars.
38 | public struct StackedHorizontalBarChartView: View {
39 | /// An array of data points to draw
40 | let dataPoints: [DataPoint]
41 |
42 | @Environment(\.chartStyle) private var chartStyle
43 | private var style: StackedHorizontalBarChartStyle {
44 | (chartStyle as? StackedHorizontalBarChartStyle) ?? .init()
45 | }
46 |
47 | /**
48 | Creates a new horizontally stacked bar chart view
49 |
50 | - Parameters:
51 | - dataPoints: An array of data points to draw
52 | */
53 | public init(dataPoints: [DataPoint]) {
54 | self.dataPoints = dataPoints
55 | }
56 |
57 | public var body: some View {
58 | VStack {
59 | GeometryReader { geometry in
60 | HStack(spacing: style.spacing) {
61 | ForEach(dataPoints) { dataPoint in
62 | Rectangle()
63 | .fill(dataPoint.legend.color)
64 | .frame(width: dataPoint.endValue / sum * geometry.size.width)
65 | .accessibilityValue(Text(String(dataPoint.endValue)) + Text(dataPoint.label))
66 | .accessibilityLabel(dataPoint.legend.label)
67 | }
68 | }
69 | .clipShape(RoundedRectangle(cornerRadius: style.cornerRadius, style: .continuous))
70 | }
71 |
72 | if style.showLegends {
73 | LegendView(dataPoints: dataPoints)
74 | .padding(.top)
75 | .accessibility(hidden: true)
76 | }
77 | }
78 | }
79 |
80 | private var sum: CGFloat {
81 | dataPoints.map(\.endValue).reduce(0.0, +)
82 | }
83 | }
84 |
85 | struct StackedHorizontalBarChartView_Previews: PreviewProvider {
86 | static var previews: some View {
87 | let protein = Legend(color: .green, label: "Protein", order: 1)
88 | let carbs = Legend(color: .blue, label: "Carbs", order: 2)
89 | let fat = Legend(color: .red, label: "Fat", order: 3)
90 |
91 | return List {
92 | StackedHorizontalBarChartView(
93 | dataPoints: [
94 | .init(value: 44, label: "Breast", legend: protein),
95 | .init(value: 77, label: "Couscous", legend: carbs),
96 | .init(value: 6, label: "Fats", legend: fat)
97 | ]
98 | )
99 | .frame(minHeight: 100)
100 | .padding(.vertical)
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import SwiftUIChartsTests
3 |
4 | var tests = [XCTestCaseEntry]()
5 | tests += SwiftUIChartsTests.allTests()
6 | XCTMain(tests)
7 |
--------------------------------------------------------------------------------
/Tests/SwiftUIChartsTests/SwiftUIChartsTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SwiftUICharts
3 |
4 | final class SwiftUIChartsTests: XCTestCase {
5 |
6 | static var allTests: [(String, () -> Void)] = [
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/Tests/SwiftUIChartsTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(SwiftUIChartsTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/screenshots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mecid/SwiftUICharts/1d55ace019a66c50e06412ceda993a856b9bee61/screenshots.png
--------------------------------------------------------------------------------