如何对齐饼图(或)饼图切片中心上的文本?

How to Alligned text on Piechart (or) piechart slice center?

我想在饼图中添加文本。

UIGraphicsPushContext(context);
CGContextMoveToPoint(context, newCenterX, newCenterY);
CGContextAddArc(context, newCenterX, newCenterY, radius,arcOffset-(myAngle/2),arcOffset+(myAngle/2), 0);
UIColor *color = [PIECHART_COLORS objectAtIndex:i%6];
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextClosePath(context);
CGContextFillPath(context);
CGMutablePathRef arcPath = CGPathCreateMutable();
CGPathAddArc(arcPath, NULL, self.frame.size.width/2, self.frame.size.height/2, radius, arcOffset-(myAngle/2), arcOffset+(myAngle/2),0);
CGRect rect =CGPathGetBoundingBox(arcPath);

rect = CGRectMake(rect.origin.x+rect.size.width/2, rect.origin.y+rect.size.height/2, 80, 20);

// For InnerCircle Empty

CGContextMoveToPoint(context, newCenterX, newCenterY);
float innercircleRadius = IS_IPAD ? 70 : 40;
CGContextAddArc(context, newCenterX, newCenterY, radius-innercircleRadius,arcOffset-(myAngle/2),arcOffset+(myAngle/2), 0);
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextClosePath(context);
CGContextFillPath(context);

从上面我可以绘制内部白色的饼图 space 但我无法在特定饼图切片中添加文本。谁能帮忙举个好例子?

我的饼图显示完美,但文本对齐方式不正确。

//对于文本我会试试这个代码

NSString *text = [NSString stringWithFormat:@"%.2f %%",[[[myArray objectAtIndex:i] objectForKey:@"Count"] floatValue]];
[text drawInRect: rect withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16], NSForegroundColorAttributeName:[UIColor redColor]} ];

您不需要绘制内圆,只需使用贝塞尔路径绘制切片即可。

https://developer.apple.com/documentation/uikit/uibezierpath https://developer.apple.com/library/content/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/BezierPaths/BezierPaths.html#//apple_ref/doc/uid/TP40010156-CH11-SW1

UIBezierPath *aPath = [UIBezierPath bezierPath];
  1. 先画线(rx+offset, ry+offset)到(rx+offset+10, ry+offset+10) [aPath addLineToPoint:]
  2. 绘制外弧[aPath addarc:]
  3. 画线 (rx+offset+10, ry+offset+10) 到 (rx+offset, ry+offset) [aPath addLineToPoint:]
  4. 画内弧[aPath addarc:]
  5. 关闭路径

或者只设置贝塞尔路径的线宽,然后用圆弧绘制贝塞尔路径即可。 [UIBezierPath bezierPathWithArcCenter:]

然后就可以直接在bezier路径里面添加文字了。 Write text inside UIBezierPath

假设您正在尝试匹配示例,并假设您正在尝试对齐显示为 "Firefox"、"IE7"、"IE6" 等的文本框...

饼图周围的每个文本框的中心都位于饼图周围的另一个不可见圆圈上。您可以想象一条线,它从中心指向,通过每个弧的中心点,到达文本框架所在的理论圆。

一旦你计算出那个不可见的外圆上的点,你就会有一个放置 UILabel 的中心点(为简单起见)。

如果您指的是中心标签,但它没有显示,那么您很可能遇到了绘制顺序问题。在这种情况下,白色圆圈绘制在文本之上。在 CGGraphics 上下文中,所有内容都是在 painter 方法中绘制的,这意味着您绘制的所有内容都将绘制 "on top of" 之前的作品。在这种情况下,文本必须是您最后绘制的内容(或者,至少它必须在白色中心圆之后绘制)

尽管您的问题很不清楚,您希望如何将 label 准确地居中到 pie slice。我尝试了我从上面发布的问题中理解的内容。

首先是使用 UIBezierPath 而不是 CGContext。我用 Parametric equaction of circle 来计算我的圆的边界点。

CGPoint newPoints = CGPointMake(a + r * cos(theta), b + r * sin(theta));
// theta = angle
// r = radius
// a = centerX
// b = centerY

它将为您提供需要显示标签的点。现在您只需计算正确的角度(切片中心)即可实现您的目标。

UIBezierPath 有一个创建圆弧的方法 [bezierPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];

下面一步步分享代码:

两个宏:

#define TO_DEGREE(x) (x * 180.0)/M_PI
#define TO_RADIAN(x) (x * M_PI)/180.0

viewDidLoad: 方法为我的图表初始化虚拟数据。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    chartView.backgroundColor = [UIColor clearColor];

    /// Assuming dummy data for our chartView
    NSArray *gendersData = @[@{@"color": [UIColor redColor], @"title": @"Boys", @"percentage": @40},
                         @{@"color": [UIColor greenColor], @"title": @"Girls", @"percentage": @45},
                         @{@"color": [UIColor purpleColor], @"title": @"Unknown", @"percentage": @15}];

    [self preparePieChartForData:gendersData];

}

preparePieChartForData: 方法

- (void)preparePieChartForData:(NSArray *)data {

    /// We will use Parametric equaction of circle to get the boundary of circle
    /*
     * Parametric equation of circle
     * x = a + r cos t
     * y = b + r sin ⁡t
     * a, b are center of circle
     * t (theta) is angle
     * x and y will be points which are on circumference of circle
     *
               270
                |
            _   |   _
                |
     180 -------o------- 360
                |
            +   |   +
                |
                90
     *
     */

    /// Thats why starting from 270.0
    CGFloat lastAngle = 270.0;
    for (NSDictionary *genderData in data) {

        /// Getting data from dictionary
        CGFloat percentage = [genderData[@"percentage"] floatValue];
        UIColor *color = genderData[@"color"];
        NSString *title = genderData[@"title"];

        /// Calculating the angle from percentage, 360 is full circle.
        CGFloat angle = lastAngle + (360 * percentage)/100.0;
        [self makeSliceStartFrom:lastAngle endAt:angle radius:80.0 color: color title:title];

        /// Updating lastAngle so that next angle can start just after this
        lastAngle = angle;
    }
}

饼图切片方法

- (void)makeSliceStartFrom:(CGFloat)startAngle
                     endAt:(CGFloat)endAngle
                    radius:(CGFloat)radius
                     color:(UIColor *)color
                     title:(NSString *)title {

    /// Converting degree to radians as bezierPath accept angle in radians
    CGFloat endAngleInRadians   = TO_RADIAN(endAngle);
    CGFloat startAngleInRadians = TO_RADIAN(startAngle);
    if (endAngle >= -180.0 && endAngle < -90.0) {
        /// This is because of above diagram
        startAngleInRadians = TO_RADIAN(-90);
    }

    /// This is the center of chartView
    CGPoint center = CGPointMake(chartView.bounds.size.width/2.0, chartView.bounds.size.height/2.0);

    /// Initializing Bezeir path
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    [bezierPath addArcWithCenter:center radius:radius startAngle:startAngleInRadians endAngle:endAngleInRadians clockwise:YES];

    /// Line width of pie chart
    CGFloat lineWidth = 30.0;
    CGPathRef slicePath = bezierPath.CGPath;

    /// Making shape layer from the path
    CAShapeLayer *sliceLayer = [CAShapeLayer layer];
    sliceLayer.path = slicePath;
    sliceLayer.strokeColor = color.CGColor;
    sliceLayer.lineWidth = lineWidth;
    sliceLayer.fillColor = [UIColor clearColor].CGColor;
    [chartView.layer addSublayer:sliceLayer];

    /*
     * ------------- LABEL PART -------------
     * Adding label at center of the slice
     */

    /// Creating an empty label
    UILabel *lbl = [[UILabel alloc] init];
    lbl.font = [UIFont systemFontOfSize:10.0];
    lbl.text = title;
    [lbl sizeToFit];

    /// theta is the center (middle) angle of this slice
    CGFloat theta = startAngleInRadians + (endAngleInRadians - startAngleInRadians)/2.0;

    /// Adding lineWith and 10.0 extra in radius so that label can visible outside of the circle
    CGFloat r = radius + lineWidth + 10.0;
    CGFloat a = center.x;
    CGFloat b = center.y;

    /// Calculating points from theta and angle by using parametric equation of cricle
    CGPoint newPoints = CGPointMake(a + r * cos(theta), b + r * sin(theta));
    CGRect  frame = lbl.frame;

    /// Recalculating the origin so that label can be exact center of slice. newPoints are (0, 0) position of label.
    frame.origin = CGPointMake(newPoints.x - frame.size.width/2.0, newPoints.y - frame.size.height/2.0);
    lbl.frame = frame;
    [chartView addSubview:lbl];
}

chartView 是故事板中 self.view 添加的 UIView。它的大小为 (300.0, 300.0) 并位于屏幕中央。下面是输出:

我尽量在回答中涵盖所有内容,如果仍有任何不清楚的地方,请随时发表评论。为了节省时间,我还附上了我的 sample project

希望对您有所帮助!