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.
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.
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;
}
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;
}