How could I customize the pie chart to draw the ring chart like below:
I gonna use pie chart from https://github.com/ChartsOrg/Charts, is it possible to achieve this? Or I've to draw this with CoreGraphics?
How should I deal with the gap and the start position?
import Charts
class ViewController: UIViewController {
var pieChartView: PieChartView!
override func viewDidLoad() {
super.viewDidLoad()
// 初始化 PieChartView
pieChartView = PieChartView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
pieChartView.center = self.view.center
self.view.addSubview(pieChartView)
// 设置环形图样式
setupPieChart()
setPieChartData()
}
func setupPieChart() {
pieChartView.holeRadiusPercent = 0.7 // 中心空洞的大小
pieChartView.transparentCircleRadiusPercent = 0.75 // 外层透明圆环的大小
pieChartView.chartDescription?.enabled = false // 关闭描述
pieChartView.drawHoleEnabled = true // 开启中心空洞
pieChartView.rotationAngle = 270.0 // 旋转到顶部
pieChartView.rotationEnabled = false // 禁用旋转
pieChartView.isUserInteractionEnabled = false // 禁用用户交互
pieChartView.legend.enabled = false // 隐藏图例
}
func setPieChartData() {
let entries = [
PieChartDataEntry(value: 60), // 已使用部分
PieChartDataEntry(value: 40) // 剩余部分
]
let dataSet = PieChartDataSet(entries: entries, label: nil)
dataSet.colors = [UIColor.red, UIColor.lightGray] // 设置不同部分的颜色
dataSet.sliceSpace = 2 // 设置分片之间的间隙
let data = PieChartData(dataSet: dataSet)
pieChartView.data = data
// 中心内容
pieChartView.centerText = "1081"
pieChartView.centerAttributedText = NSAttributedString(string: "还可以吃")
}
}
This code seems not working as expected.
//Edit in 2024/10/12
finally, I finish it like this. leave it here, hope it will help someone.
#import "JFDiaryCalorieGrpahView.h"
@interface JFDiaryCalorieGrpahView ()
@property (nonatomic, strong) UIColor* trackTintColor;
@property (nonatomic, strong) UIColor* progressTintColor;
@property (nonatomic, assign) CGFloat insets;
@property (nonatomic, assign) CGFloat lineWidth;
@property (nonatomic, assign) CGFloat startAngle;
@property (nonatomic, assign) CGFloat endAngle;
@property (nonatomic, assign) CGFloat rotateAngle;
@property (nonatomic, assign) BOOL clockwise;
@end
@implementation JFDiaryCalorieGrpahView
-(void)setProgress:(CGFloat)progress {
_progress = progress;
[self setNeedsDisplay];
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:CGRectZero];
self.backgroundColor = [UIColor clearColor];
if (self) {
self.translatesAutoresizingMaskIntoConstraints = NO;
_trackTintColor = NA_Color_Name(@"#FEE9E5");
_progressTintColor = NA_Color_Name(@"#FF5753");
_insets = 2;
_lineWidth = 10;
_startAngle = 0;
_endAngle = 250;
_rotateAngle = 216;
_clockwise = YES;
}
return self;
}
-(void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, rect.size.width/2, rect.size.height/2);
[self drawTrack:rect];
[self drawProgress:rect];
CGContextRestoreGState(context);
}
-(void)drawTrack:(CGRect)rect {
CGFloat radius = MIN(rect.size.width, rect.size.height)/2 - self.lineWidth/2 - self.insets;
CGFloat rotation = [self convetAngleToRadian:self.rotateAngle];
CGFloat modifiedStart = [self convetAngleToRadian:self.startAngle] - rotation;
CGFloat modifiedEnd = [self convetAngleToRadian:self.endAngle] - rotation;
UIBezierPath* trackPath = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:radius startAngle:modifiedStart endAngle:modifiedEnd clockwise:self.clockwise];
trackPath.lineWidth = self.lineWidth;
trackPath.lineCapStyle = kCGLineCapRound;
[self.trackTintColor setStroke];
[trackPath stroke];
}
-(CGFloat)convetAngleToRadian:(CGFloat)angle {
return angle / 180.0 * M_PI;
}
-(void)drawProgress:(CGRect)rect {
if (self.progress == 0) return;
CGFloat radius = MIN(rect.size.width, rect.size.height)/2 - self.lineWidth/2 - self.insets;
CGFloat rotation = [self convetAngleToRadian:self.rotateAngle];
CGFloat modifiedStart = [self convetAngleToRadian:self.startAngle] - rotation;
CGFloat deta = [self convetAngleToRadian:(self.endAngle - self.startAngle) * self.progress];
CGFloat modifiedEnd = modifiedStart + deta;
// Main arc with flat ends
UIBezierPath* trackPath = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:radius startAngle:modifiedStart endAngle:modifiedEnd clockwise:self.clockwise];
trackPath.lineWidth = self.lineWidth;
trackPath.lineCapStyle = kCGLineCapButt; // Flat ends
[self.progressTintColor setStroke];
[trackPath stroke];
// Add round cap at the start or end
if (self.progress > 0) {
CGPoint startCapCenter = CGPointMake(radius * cos(modifiedStart), radius * sin(modifiedStart));
UIBezierPath* startCapPath = [UIBezierPath bezierPathWithArcCenter:startCapCenter radius:self.lineWidth/2 startAngle:0 endAngle:2 * M_PI clockwise:YES];
[self.progressTintColor setFill];
[startCapPath fill];
}
if (self.progress == 1) {
CGPoint endCapCenter = CGPointMake(radius * cos(modifiedEnd), radius * sin(modifiedEnd));
UIBezierPath* endCapPath = [UIBezierPath bezierPathWithArcCenter:endCapCenter radius:self.lineWidth/2 startAngle:0 endAngle:2 * M_PI clockwise:YES];
[self.progressTintColor setFill];
[endCapPath fill];
}
}
@end
I think you don’t need the PieChart here, you can simply modified this Arc View
import SwiftUI
struct Arc: InsettableShape {
let startAngle: Angle
let endAngle: Angle
let clockWise: Bool
var insetAmount = 0.0
func path(in rect: CGRect) -> Path {
var path = Path()
let rotationAdjustment = Angle.degrees(90)
let modifiedStart = startAngle - rotationAdjustment
let modifiedEnd = endAngle - rotationAdjustment
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2 - insetAmount, startAngle: modifiedStart, endAngle: modifiedEnd, clockwise: !clockWise)
return path
}
func inset(by amount: CGFloat) -> some InsettableShape {
var arc = self
arc.insetAmount += amount
return arc
}
}
struct ContentView: View {
var body: some View {
Arc(startAngle: .degrees(0), endAngle: .degrees(200), clockWise: true)
.strokeBorder(.blue, lineWidth: 30)
}
}
#Preview {
ContentView()
}