My UI designer asked me to build this donut pie chart and I have been banging my head against the wall for days! It seems impossible. Is there a way to do this in SwiftUI or should I ask the designer for a design that's actually implementable?
I'm able to make the below, but then I'm stuck. I can't get the separation between each segment and I can't get the rounded corners. I thought about a border but that doesn't work. Any thoughts?:
import SwiftUI
struct ContentView: View {
@ObservedObject var charDataObj = ChartDataContainer()
@State var indexOfTappedSlice = -1
var body: some View {
VStack {
Spacer()
ZStack {
Circle()
.stroke(Color(hex: 0x2A3950), style: StrokeStyle(lineWidth: 100, lineCap: .round, lineJoin: .round))
.frame(width: 220, height: 270)
ForEach(0..<charDataObj.chartData.count) { index in
Circle()
.trim(from: index == 0 ? 0.0 : charDataObj.chartData[index-1].value / 100,
to: (charDataObj.chartData[index].value / 100))
.stroke(charDataObj.chartData[index].color, style: StrokeStyle(lineWidth: 50.0, lineCap: .butt, lineJoin: .round))
.rotationEffect(Angle(degrees: 270.0))
.onTapGesture {
indexOfTappedSlice = indexOfTappedSlice == index ? -1 : index
}
.scaleEffect(index == indexOfTappedSlice ? 1.1 : 1.0)
.animation(.spring())
}
}.frame(width: 150, height: 150)
Spacer()
}
.frame(maxWidth: .infinity)
.background(Color(hex: 0x42506B))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
//MARK:- Chart Data
struct ChartData {
var id = UUID()
var color : Color
var percent : CGFloat
var value : CGFloat
}
@available(iOS 15.0, *)
class ChartDataContainer : ObservableObject {
@Published var chartData =
[ChartData(color: .green, percent: 26, value: 0),
ChartData(color: .blue, percent: 28, value: 0),
ChartData(color: .white, percent: 23, value: 0),
ChartData(color: .yellow, percent: 12, value: 0),
ChartData(color: .red, percent: 11, value: 0),]
init() {
calc()
}
func calc(){
var value : CGFloat = 0
for i in 0..<chartData.count {
value += chartData[i].percent
chartData[i].value = value
}
}
}
extension Color {
init(hex: Int, opacity: Double = 1.0) {
let red = Double((hex & 0xff0000) >> 16) / 255.0
let green = Double((hex & 0xff00) >> 8) / 255.0
let blue = Double((hex & 0xff) >> 0) / 255.0
self.init(.sRGB, red: red, green: green, blue: blue, opacity: opacity)
}
}
I can get some separation with an adjustment to the trim, but it looks awful:
.trim(from: index == 0 ? 0.003: (charDataObj.chartData[index-1].value / 100) + 0.003,
to: (charDataObj.chartData[index].value / 100) - 0.003)
This is now possible in iOS 17, and it's very easy:
Chart(dailySales, id:\.day) { element in
SectorMark(
angle: .value("Sales", element.sales),
innerRadius: .ratio(0.55),
angularInset: 2
)
.cornerRadius(10)
.foregroundStyle(element.color)
}
.rotationEffect(Angle(degrees: -35))