Chart Animations: Custom

TKChart uses the Core Animation infrastructure available on iOS that you use to animate the visual points in series. In order to enable the animations, you should set allowAnimations property to YES. In that case the default animations are performed for each series. If you handle the TKChartDelegate protocol and implement the chart:animationForSeries:withState:inRect: method, you can perform custom animations. With Core Animation, most of the work required to draw each frame of an animation is done for you. All you have to do is configure a few animation parameters (such as the start and end points).

You can use most of the Core Animation framework to customize the visual points animation. You can read more about Core Animation at Apple Developer website.

Configuration Prerequisites

You should handle the TKChartDelegate's method chart:animationForSeries:withState:inRect: to create a custom animation. In addition, you should group the animation created for each point in CAAnimationGroup to apply animation sequentially. You can access old and new points collection by using the TKChartSeriesRenderState properties oldPoints and points. It allows generation for value key path property for point at a specified index by calling the animationKeyPathForPointAtIndex method.

Animating Line Series

The code below animates the line series points by moving them from bottom to top.

- (CAAnimation *)chart:(TKChart *)chart animationForSeries:(TKChartSeries *)series withState:(TKChartSeriesRenderState *)state inRect:(CGRect)rect
{
    CFTimeInterval duration = 0;
    NSMutableArray *animations = [[NSMutableArray alloc] init];
    for (int i = 0; i<state.points.count; i++) {

        if (_grow) {
            NSString *keyPath = [NSString stringWithFormat:@"seriesRenderStates.%lu.points.%d.x", (unsigned long)series.index, i];
            TKChartVisualPoint *point = [state.points objectAtIndex:i];

            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
            animation.duration = 0.1 *(i + 0.2);
            animation.fromValue = @0;
            animation.toValue = @(point.x);
            animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
            [animations addObject:animation];

            duration = animation.duration;
        }
        else {
            NSString *keyPath = [NSString stringWithFormat:@"seriesRenderStates.%lu.points.%d.y", (unsigned long)series.index, i];
            TKChartVisualPoint *point = [state.points objectAtIndex:i];
            CGFloat oldY = rect.size.height;

            if (i > 0) {
                CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keyPath];
                animation.duration = 0.1* (i + 1);
                animation.values = @[ @(oldY), @(oldY), @(point.y) ];
                animation.keyTimes = @[ @0, @(i/(i+1.)), @1 ];
                animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
                [animations addObject:animation];

                duration = animation.duration;
            }
            else {
                CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
                animation.fromValue = @(oldY);
                animation.toValue = @(point.y);
                animation.duration = 0.1f;
                [animations addObject:animation];
            }
        }
    }

    CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
    group.duration = duration;
    group.animations = animations;

    return group;
}
func chart(_ chart: TKChart, animationFor series: TKChartSeries, with state: TKChartSeriesRenderState, in rect: CGRect) -> CAAnimation? {
    var duration = 0.0
    var animations = [CAAnimation]()
    for i in 0..<state.points.count() {
        if grow {
            let keyPath = "seriesRenderStates.\(series.index).points.\(i).x"
            let point = state.points.object(at: i) as! TKChartVisualPoint
            let animation = CABasicAnimation(keyPath: keyPath as String)
            animation.duration = 0.1*(Double(i)+0.2)
            animation.fromValue = 0
            animation.toValue = point.x
            animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
            animations.append(animation)

            duration = animation.duration
        }
        else {
            let keyPath = "seriesRenderStates.\(series.index).points.\(i).y"
            let point = state.points.object(at: i) as! TKChartVisualPoint
            let oldY = rect.size.height as CGFloat

            if i > 0 {
                let animation = CAKeyframeAnimation(keyPath: keyPath)
                animation.duration = 0.1*(Double(i)+1.0)
                animation.values = [oldY, oldY, point.y]
                animation.keyTimes = [0, (i/(i+1)) as NSNumber, 1]
                animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
                animations.append(animation)

                duration = animation.duration
            }
            else {
                let animation = CABasicAnimation(keyPath: keyPath)
                animation.fromValue = oldY
                animation.toValue = point.y
                animation.duration = 0.1
                animations.append(animation)
            }
        }
    }

    let group = CAAnimationGroup()
    group.duration = duration
    group.animations = animations

    return group
}
public override CAAnimation AnimationForSeries (TKChart chart, TKChartSeries series, TKChartSeriesRenderState state, CGRect rect)
{
    double duration = 0;
    List<CAAnimation> animations = new List<CAAnimation> ();

    for (int i = 0; i<(int)state.Points.Count; i++) 
    {            
        TKChartVisualPoint point = (TKChartVisualPoint)state.Points.ObjectAtIndex ((uint)i);

        if (Grow) 
        {
            string keyPath = string.Format ("seriesRenderStates.{0}.points.{1}.x", series.Index, i);

            CABasicAnimation animation = (CABasicAnimation)CABasicAnimation.FromKeyPath(keyPath);
            animation.Duration = 0.1 *(i + 0.2);
            animation.From = new NSNumber(0);
            animation.To = new NSNumber(point.X);
            animation.TimingFunction = CAMediaTimingFunction.FromName (CAMediaTimingFunction.EaseOut);
            animations.Add(animation);

            duration = animation.Duration;
        }
        else 
        {
            string keyPath = string.Format ("seriesRenderStates.{0}.points.{1}.y", series.Index, i);
            nfloat oldY = rect.Height;

            if (i > 0) 
            {
                CAKeyFrameAnimation animation = (CAKeyFrameAnimation)CAKeyFrameAnimation.GetFromKeyPath(keyPath);
                animation.Duration = 0.1* (i + 1);
                animation.Values = new NSNumber[] { new NSNumber(oldY), new NSNumber(oldY), new NSNumber(point.Y) };
                animation.KeyTimes = new NSNumber[] { new NSNumber(0), new NSNumber(i/(i+1.0)), new NSNumber(1) };
                animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseOut);
                animations.Add (animation);

                duration = animation.Duration;
            }
            else 
            {
                CABasicAnimation animation = (CABasicAnimation)CABasicAnimation.FromKeyPath(keyPath);
                animation.From = new NSNumber(oldY);
                animation.To = new NSNumber(point.Y);
                animation.Duration = 0.1f;
                animations.Add(animation);
            }
        }
    }

    CAAnimationGroup group = new CAAnimationGroup ();
    group.Duration = duration;
    group.Animations = animations.ToArray();

    return group;
}

Animating Pie Series

The following lines of code animate the pie's segments by moving them to the pie center together with changing their opacity.

- (CAAnimation *)chart:(TKChart *)chart animationForSeries:(TKChartSeries *)series withState:(TKChartSeriesRenderState *)state inRect:(CGRect)rect
{
    CFTimeInterval duration = 0;
    NSMutableArray *animations = [[NSMutableArray alloc] init];
    for (int i = 0; i<state.points.count; i++) {
        NSString *pointKeyPath = [state animationKeyPathForPointAtIndex:i];

        NSString *keyPath = [NSString stringWithFormat:@"%@.distanceFromCenter", pointKeyPath];
        CAKeyframeAnimation *a = [CAKeyframeAnimation animationWithKeyPath:keyPath];
        a.values = @[ @50, @50, @0 ];
        a.keyTimes = @[ @0, @(i/(i+1.)), @1 ];
        a.duration = 0.3 * (i+1.1);
        [animations addObject:a];

        keyPath = [NSString stringWithFormat:@"%@.opacity", pointKeyPath];
        a = [CAKeyframeAnimation animationWithKeyPath:keyPath];
        a.values = @[ @0, @0, @1 ];
        a.keyTimes = @[ @0, @(i/(i+1.)), @1 ];
        a.duration = 0.3 * (i+1.1);
        [animations addObject:a];

        duration = a.duration;
    }
    CAAnimationGroup *g = [[CAAnimationGroup alloc] init];
    g.duration = duration;
    g.animations = animations;
    return g;
}
func chart(_ chart: TKChart, animationFor series: TKChartSeries, with state: TKChartSeriesRenderState, in rect: CGRect) -> CAAnimation? {
    var duration = 0.0
    var animations = [CAAnimation]()
    for i in 0..<state.points.count() {
        let pointKeyPath = state.animationKeyPathForPoint(at: i)!
        let keyPath = NSString(format: "%@.distanceFromCenter", pointKeyPath)
        var a = CAKeyframeAnimation(keyPath: keyPath as String)
        let interval = 0.3*(Double(i)+1.1)

        a.values = [50, 50, 0]
        a.keyTimes = [0.0, (Double(i)/(Double(i)+1.0)) as NSNumber, 1.0]
        a.duration = interval
        animations.append(a)

        a = CAKeyframeAnimation(keyPath: "\(pointKeyPath).opacity")
        a.values = [0, 0, 1]
        a.keyTimes = [0.0, (Double(i)/(Double(i)+1.0)) as NSNumber, 1.0]
        a.duration = interval
        animations.append(a)

        duration = interval
    }
    let g = CAAnimationGroup()
    g.duration = duration
    g.animations = animations
    return g
}
public override CAAnimation AnimationForSeries (TKChart chart, TKChartSeries series, TKChartSeriesRenderState state, CGRect rect)
{
    double duration = 0;
    List<CAAnimation> animations = new List<CAAnimation>();
    for (int i = 0; i<(int)state.Points.Count; i++) {
        string pointKeyPath = state.AnimationKeyPathForPointAtIndex ((uint)i);

        string keyPath = string.Format("{0}.distanceFromCenter", pointKeyPath);
        CAKeyFrameAnimation a = CAKeyFrameAnimation.GetFromKeyPath(keyPath);
        a.Values = new NSNumber[] { new NSNumber(50), new NSNumber(50), new NSNumber(0) };
        a.KeyTimes = new NSNumber[] { new NSNumber(0), new NSNumber(i/(i+1.0)), new NSNumber(1) };
        a.Duration = 0.3 * (i+1.1);
        animations.Add(a);

        keyPath = string.Format("{0}.opacity", pointKeyPath);
        a = CAKeyFrameAnimation.GetFromKeyPath(keyPath);
        a.Values = new NSNumber[] { new NSNumber(0), new NSNumber(0), new NSNumber(1) };
        a.KeyTimes = new NSNumber[] { new NSNumber(0), new NSNumber(i/(i+1.0)), new NSNumber(1) };
        a.Duration = 0.3 * (i+1.1);
        animations.Add(a);

        duration = a.Duration;
    }
    CAAnimationGroup g = new CAAnimationGroup();
    g.Duration = duration;
    g.Animations = animations.ToArray();
    return g;
}