1 |
efrain |
1 |
//
|
|
|
2 |
// PieChartView.swift
|
|
|
3 |
// twogetskills
|
|
|
4 |
//
|
|
|
5 |
// Created by Efrain Yanez Recanatini on 3/10/22.
|
|
|
6 |
//
|
|
|
7 |
|
|
|
8 |
import SwiftUI
|
|
|
9 |
|
|
|
10 |
@available(OSX 10.15, *)
|
|
|
11 |
public struct PieChartView: View {
|
|
|
12 |
public let values: [Double]
|
|
|
13 |
public var total: Double
|
|
|
14 |
public let names: [String]
|
|
|
15 |
public let formatter: (Double) -> String
|
|
|
16 |
|
|
|
17 |
public var colors: [Color]
|
|
|
18 |
public var backgroundColor: Color
|
|
|
19 |
|
|
|
20 |
public var widthFraction: CGFloat
|
|
|
21 |
public var innerRadiusFraction: CGFloat
|
|
|
22 |
public var chartSize : CGFloat
|
|
|
23 |
|
|
|
24 |
|
|
|
25 |
|
|
|
26 |
@State private var activeIndex: Int = -1
|
|
|
27 |
|
|
|
28 |
var slices: [PieSliceData] {
|
|
|
29 |
let sum = values.reduce(0, +)
|
|
|
30 |
var endDeg: Double = 0
|
|
|
31 |
var tempSlices: [PieSliceData] = []
|
|
|
32 |
|
|
|
33 |
for (i, value) in values.enumerated() {
|
|
|
34 |
let degrees: Double = value * 360 / sum
|
|
|
35 |
tempSlices.append(PieSliceData(startAngle: Angle(degrees: endDeg), endAngle: Angle(degrees: endDeg + degrees), text: "", color: self.colors[i]))
|
|
|
36 |
endDeg += degrees
|
|
|
37 |
}
|
|
|
38 |
|
|
|
39 |
//text: String(format: "%.0f%%", value * 100 / sum)
|
|
|
40 |
return tempSlices
|
|
|
41 |
}
|
|
|
42 |
|
|
|
43 |
public init(values:[Double], names: [String] = [], formatter: @escaping (Double) -> String, colors: [Color] = [Color.blue, Color.green, Color.orange], backgroundColor: Color = Color(red: 21 / 255, green: 24 / 255, blue: 30 / 255, opacity: 1.0), widthFraction: CGFloat = 0.75, innerRadiusFraction: CGFloat = 0.60, chartSize : CGFloat = 250, total : Double = 0){
|
|
|
44 |
self.values = values
|
|
|
45 |
self.names = names
|
|
|
46 |
self.formatter = formatter
|
|
|
47 |
|
|
|
48 |
self.colors = colors
|
|
|
49 |
self.backgroundColor = backgroundColor
|
|
|
50 |
self.widthFraction = widthFraction
|
|
|
51 |
self.innerRadiusFraction = innerRadiusFraction
|
|
|
52 |
|
|
|
53 |
self.chartSize = chartSize
|
|
|
54 |
self.total = total
|
|
|
55 |
}
|
|
|
56 |
|
|
|
57 |
public var body: some View {
|
|
|
58 |
GeometryReader { geometry in
|
|
|
59 |
|
|
|
60 |
VStack{
|
|
|
61 |
ZStack{
|
|
|
62 |
ForEach(0..<self.values.count){ i in
|
|
|
63 |
PieSlice(pieSliceData: self.slices[i])
|
|
|
64 |
.scaleEffect(self.activeIndex == i ? 1.03 : 1)
|
|
|
65 |
.animation(Animation.spring())
|
|
|
66 |
}
|
|
|
67 |
/*.frame(width: widthFraction * geometry.size.width, height: widthFraction * geometry.size.width)*/
|
|
|
68 |
.frame(width: widthFraction * self.chartSize, height: widthFraction * self.chartSize)
|
|
|
69 |
.gesture(
|
|
|
70 |
DragGesture(minimumDistance: 0)
|
|
|
71 |
.onChanged { value in
|
|
|
72 |
/*
|
|
|
73 |
let radius = 0.5 * widthFraction * geometry.size.width
|
|
|
74 |
*/
|
|
|
75 |
let radius = 0.5 * self.chartSize * geometry.size.width
|
|
|
76 |
let diff = CGPoint(x: value.location.x - radius, y: radius - value.location.y)
|
|
|
77 |
let dist = pow(pow(diff.x, 2.0) + pow(diff.y, 2.0), 0.5)
|
|
|
78 |
if (dist > radius || dist < radius * innerRadiusFraction) {
|
|
|
79 |
self.activeIndex = -1
|
|
|
80 |
return
|
|
|
81 |
}
|
|
|
82 |
var radians = Double(atan2(diff.x, diff.y))
|
|
|
83 |
if (radians < 0) {
|
|
|
84 |
radians = 2 * Double.pi + radians
|
|
|
85 |
}
|
|
|
86 |
|
|
|
87 |
for (i, slice) in slices.enumerated() {
|
|
|
88 |
if (radians < slice.endAngle.radians) {
|
|
|
89 |
self.activeIndex = i
|
|
|
90 |
break
|
|
|
91 |
}
|
|
|
92 |
}
|
|
|
93 |
}
|
|
|
94 |
.onEnded { value in
|
|
|
95 |
self.activeIndex = -1
|
|
|
96 |
}
|
|
|
97 |
)
|
|
|
98 |
/*
|
|
|
99 |
Circle()
|
|
|
100 |
.fill(self.backgroundColor)
|
|
|
101 |
.frame(width: widthFraction * geometry.size.width * innerRadiusFraction, height: widthFraction * geometry.size.width * innerRadiusFraction)*/
|
|
|
102 |
|
|
|
103 |
Circle()
|
|
|
104 |
.fill(Color.white)
|
|
|
105 |
.frame(width: widthFraction * self.chartSize * innerRadiusFraction, height: widthFraction * self.chartSize * innerRadiusFraction)
|
|
|
106 |
|
|
|
107 |
VStack {
|
|
|
108 |
/*
|
|
|
109 |
Text(self.activeIndex == -1 ? "Total" : names[self.activeIndex])
|
|
|
110 |
.font(.title)
|
|
|
111 |
.foregroundColor(Color.gray)
|
|
|
112 |
|
|
|
113 |
Text(self.formatter(self.activeIndex == -1 ? values.reduce(0, +) : values[self.activeIndex]))
|
|
|
114 |
.font(.title)
|
|
|
115 |
*/
|
|
|
116 |
|
|
|
117 |
Text( self.formatter(total))
|
|
|
118 |
.font(.title)
|
|
|
119 |
}
|
|
|
120 |
|
|
|
121 |
}
|
|
|
122 |
//PieChartRows(colors: self.colors, names: self.names, values: self.values.map { self.formatter($0) }, percents: self.values.map { String(format: "%.0f%%", $0 * 100 / self.values.reduce(0, +)) })
|
|
|
123 |
}
|
|
|
124 |
.background(self.backgroundColor)
|
|
|
125 |
.foregroundColor(Color.gray)
|
|
|
126 |
}
|
|
|
127 |
|
|
|
128 |
}
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
@available(OSX 10.15, *)
|
|
|
132 |
struct PieChartRows: View {
|
|
|
133 |
var colors: [Color]
|
|
|
134 |
var names: [String]
|
|
|
135 |
var values: [String]
|
|
|
136 |
var percents: [String]
|
|
|
137 |
|
|
|
138 |
|
|
|
139 |
var body: some View {
|
|
|
140 |
VStack{
|
|
|
141 |
ForEach(0..<self.values.count){ i in
|
|
|
142 |
HStack {
|
|
|
143 |
RoundedRectangle(cornerRadius: 5.0)
|
|
|
144 |
.fill(self.colors[i])
|
|
|
145 |
.frame(width: 20, height: 20)
|
|
|
146 |
Text(self.names[i])
|
|
|
147 |
Spacer()
|
|
|
148 |
VStack(alignment: .trailing) {
|
|
|
149 |
Text(self.values[i])
|
|
|
150 |
Text(self.percents[i])
|
|
|
151 |
.foregroundColor(Color.gray)
|
|
|
152 |
}
|
|
|
153 |
}
|
|
|
154 |
}
|
|
|
155 |
}
|
|
|
156 |
}
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
@available(OSX 10.15.0, *)
|
|
|
160 |
struct PieChartView_Previews: PreviewProvider {
|
|
|
161 |
static var previews: some View {
|
|
|
162 |
PieChartView(values: [20,80], names: ["Rent", "Transport", "Education"], formatter: {value in String(format: "%.0f%%", value)})
|
|
|
163 |
}
|
|
|
164 |
}
|
|
|
165 |
|
|
|
166 |
//"$%.2f"
|