Simple implementation of sector charts

Posted by markmil2002 on Sat, 04 Jul 2020 16:40:48 +0200

Demo Address

Main Code

1. Traverse the proportions of all sectors, calculate the start and end angles of each sector area, and then draw FanShapeLayer

int max = 0;
    for (NSNumber * obj in self.angles) {
        max += [obj intValue];
    }
    
    CGFloat startAngle = self.startAngle;// Start 0 represents the positive direction of the x-axis
    
    for (int i = 0; i < self.angles.count; i ++) {
        NSNumber * obj = [self.angles objectAtIndex:i];
        
        CGFloat rate = [obj intValue];
        CGFloat angle = rate / max * M_PI * 2.0;
        
        // Calculate the drawing angle - Endpoint The drawing direction is counterclockwise so it is subtracted
        CGFloat endAngle = startAngle - angle;
        
        // Set Colors
        UIColor * color = randomColor;
        
        // Create FanShapeLayer
        FanShapeLayer * layer = [[FanShapeLayer alloc]init];
        layer.fillColor = color.CGColor;
        layer.name = [NSString stringWithFormat:@"layer = %d",i];
        layer.startAngle = startAngle;
        layer.endAngle = endAngle;
        layer.radius = self.radius;
        layer.centerPoint = self.chartPoint;
        // Draw path (set parameters first)
        [layer drawPath];
        [self.layer addSublayer:layer];
        [self.fansArray addObject:layer];
        
        // Calculate the starting point of the next drawing
        startAngle -= angle;
    }

2. After setting the parameters of FanShapeLayer, first draw CGMutablePathRef, which is a closed sector, then assign CGMutablePathRef to FanShapeLayer

CGMutablePathRef path = CGPathCreateMutable();
    CGAffineTransform transform = CGAffineTransformMakeTranslation(1, 1);
    CGPathMoveToPoint(path, &transform, self.centerPoint.x, self.centerPoint.y);
    CGPathAddArc(path, &transform, self.centerPoint.x, self.centerPoint.y, radius, self.startAngle, self.endAngle, YES);
    CGPathCloseSubpath(path);

3. Mainly Click to zoom in. CALyer does not respond to click events. This is achieved by getting whether the point of the touch object is in the layer area or not.

You can find layers on the web that you can click on in the following ways, but here you get nil, and you don't know why.
CALayer * layer = [self.layer hitTest:point];

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // Get the center point of a click
    UITouch * touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];
    // Gets whether the click area contains a CAShapeLayer
    FanShapeLayer * layer = [self touchLayer:point];
    if (layer) {
        NSLog(@"%@", layer.name);
        // Set Selected to Unselected
        NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isSelected = YES"];
        NSArray * selectArray = [self.fansArray filteredArrayUsingPredicate:predicate];
        [selectArray makeObjectsPerformSelector:@selector(setIsSelected:) withObject:@NO];
        if ([selectArray containsObject:layer]) {
            return ;
        }
        // Add animation
        layer.isSelected = YES;
    }
}

- (FanShapeLayer *)touchLayer:(CGPoint)point{
    // Get the clicked area layer
    for (FanShapeLayer * layer in self.fansArray) {
        // Get the inPoint, coordinates in the layer
        CGPoint inPoint = [layer convertPoint:point fromLayer:self.layer];
        
        if (CGPathContainsPoint(layer.path, 0, inPoint, YES)) {
            return layer;
        }
    }
    return nil;
}

4. Finally add zoom animation when clicking

  // Add animation
        CABasicAnimation *animation = [CABasicAnimation animation];
        // Which property of the keyPath content object requires animation
        animation.keyPath = @"path";
        // Set up proxy
        animation.delegate = self;
        // The value at the end of the changed property
        animation.toValue = (__bridge id _Nullable)(self.endPath);
        // Animation duration
        animation.duration = 0.25;
        // Do not remove after completion
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeForwards;
        // Add animation
        [self addAnimation:animation forKey:nil];