Introduction
I was recently asked by someone if it is possible to get
Visiblox Charts to animate. While we don't provide an animation
framework as part of the chart's API, it really isn't hard to do
and Visiblox Charts fully supports animations. So I thought it
might be interesting to take a look at how to do that. This post
will show you how. The full source code for this example is
available for download from here. Just add a
reference to Visiblox Charts 2.1 and you're
off. We'll look at animations in the following
order:
Storyboards, Animations and DependencyObjects
Silverlight and WPF provide quite extensive animation support as
part of the framework. The main concepts involved are Storyboards
and Animations as outlined
by MSDN. Basically, a storyboard is a set of animations over a
defined period of time. Each Animation in turn, manipulates one
property of the object. Because animations animate
DependencyProperties and Visiblox Charts makes extensive use of
these, the task of animating the charts is largely just that of
plumbing together those two concepts. So let's get on to it
then.
The Setup
First, we're going to need a test bed to try out our animations
so lets set up a chart in a page. Something really simple should do
it. To keep things simple I'm going to stick to category XAxis and
linear YAxis so the code doesn't get complicated by type
conversions, but the concepts are the same regardless of axes and
data types.
So lets get a chart going:
<charts:Chart Grid.Row="0" x:Name="MainChart">
<charts:Chart.YAxis>
<charts:LinearAxis>
<charts:LinearAxis.Range>
<charts:DoubleRange Minimum="0" Maximum="100" />
</charts:LinearAxis.Range>
</charts:LinearAxis>
</charts:Chart.YAxis>
</charts:Chart>
Notice that I specify an axis range. That's really just to avoid
the axis popping into existence when the animation first adds
something to the chart as that's quite distracting.
So now we have a chart, lets look at animating some stuff.
Fade Animations
One obvious animation is the fade in/out animation. This would
be particularly effective when switching from one series type to
another or in a drill down context. So imagine, you were looking at
quarterly data, you click on a data point and it fades out the
quarterly data and fades in the monthly data, or something to that
effect.
A fade animation is essentially an animation of the Opacity
property of a visual element. For convenience (and ease of re-use
and extension), I've created an AnimationHelper class which
contains static methods which perform the animations for you. The
first method we're going to add is an animation which adds a series
to a chart for you and animates it as a fade in.
public static void AddSeriesWithFadeIn(Chart chart, IChartSeries series, int durationInMillis, EventHandler onComplete)
{
Storyboard fadeIn = new Storyboard();
fadeIn.Duration = new Duration(new TimeSpan(0, 0, 0, 0, durationInMillis));
var frameworkElem = (series as FrameworkElement);
if (series == null)
{
throw new InvalidOperationException("Can't fade non-framework element series.");
}
frameworkElem.Opacity = 0;
chart.Series.Add(series);
AddFadeAnimation(frameworkElem, fadeIn, 1.0, EasingMode.EaseIn);
fadeIn.Completed += new EventHandler((s, e) =>
{
frameworkElem.Opacity = 1;
if (onComplete != null)
{
onComplete(s, e);
}
});
fadeIn.Begin();
}
The crux of this is that it sets the inital opacity of the
series to 0, adds it to the chart and then animates the opacity to
1 over the specified duration. We've added an event handler which
will be notified on completion as well, as that might be useful in
some circumstances. The only other non-obvious bit of this is the
AddFadeAnimation method. So lets look at that.
private static void AddFadeAnimation(FrameworkElement frameworkElem, Storyboard sb, double to, EasingMode mode, EventHandler handler = null)
{
DoubleAnimation animation = new DoubleAnimation();
animation.To = to;
animation.EasingFunction = new ExponentialEase() { EasingMode = mode };
if (handler != null)
{
animation.Completed += handler;
}
Storyboard.SetTarget(animation, frameworkElem);
Storyboard.SetTargetProperty(animation, new PropertyPath("(Opacity)"));
sb.Children.Add(animation);
}
That method just does the plumbing of connecting an Animation to
the opacity of the series and adding it to the Storyboard. To use
this to animate a new series into the chart, create a new
IChartSeries instance, attach the desired DataSeries to it and let
the AnimationHelper do the rest.
The AnimationHelper in the downloadable zip contains
a method which does a cross fade as well. That method
simultaneously fades out all the series currently on the chart (and
removes them from the chart) and adds a new series to the chart and
fades it in. That gives a nice drill down sort of transition. One
thing to bear in mind is that because, as far as the chart is
concerned, for the duration of the transition both series will be
on the chart at the same time, this works best with series types
which don't affect the rendering of other series on the chart. It
looks pretty good with line or area charts but not so well with
column and bar charts. For the latter I would probably recommend
doing a fade out of the old series, followed by a fade in of the
new one which looks better in those cases.
Data Animations
The other obvious thing to animate is the data itself. For
example, instead of just attaching a new data series to your column
chart and having the chart re-render, it's quite cool to have the
columns "bounce" to the new values. Again, it's just a matter of
plumbing. Because Visiblox Charts listens to PropertyChanged events
on the IDataPoints attached to it, updating those data points will
cause the chart to redraw. So we just need a method that takes the
data series to animate, the targets for each point in that data
series and the duration of the animation and we're off.
public static void AnimateDataPointYLocations(DataSeries<string, double> from, DataSeries<string, double> to, int durationInMillis, EventHandler onComplete)
{
Storyboard sb = new Storyboard();
sb.Duration = new Duration(new TimeSpan(0, 0, 0, 0, durationInMillis));
for (int i = 0; i < from.Count; i++)
{
if (i >= to.Count)
{
break;
}
var wrapper = new DataPointWrapper(from[i]);
AddYAnimation(sb, wrapper, to[i], EasingMode.EaseOut);
}
sb.Completed += new EventHandler((s, e) =>
{
if (onComplete != null)
{
onComplete(s, e);
}
});
sb.Begin();
}
Again, pretty straight forward. We're only animating Y values
because in our example X values are strings and it wouldn't be
entirely clear how to animate those. There's no problem with
animating X values if they were doubles. Two little gotchas in this
example though: AddYAnimation and DataPointWrapper. The former, I'm
pretty sure you can guess already just plumbs the animation for
that data point into the storyboard. But let's take a look for
completeness:
private static void AddYAnimation(Storyboard sb, DataPointWrapper from, DataPoint<string, double> to, EasingMode easingMode)
{
DoubleAnimation yAnimation = new DoubleAnimation();
yAnimation.To = to.Y;
yAnimation.EasingFunction = new ExponentialEase() { EasingMode = easingMode };
Storyboard.SetTarget(yAnimation, from);
Storyboard.SetTargetProperty(yAnimation, new PropertyPath("YValue"));
sb.Children.Add(yAnimation);
}
That leaves the question of DataPointWrapper. Basically, the
reason for that class is that animations only work with subclasses
of DependencyObject. Unfortunately, DataPoint is not such a
subclass. So the answer is to create a wrapper object which extends
DependencyObject and forwards the values assigned to X and Y to the
underlying data point for the purpose of the animation.
Conclusion
This post provided a basic overview of one approach to adding
animations to Visiblox Charts. It's really not rocket surgery.
Obviously, there's a lot more that could be done, such as animating
the data as the series are added to the chart (e.g. the columns
grow from the bottom of the chart to their final values) or
animating X values or scaling or sliding animations. All that stuff
is possible but we'll maybe leave that for a future blog. In the
meantime, the code from this blog is available for download here. Just add a
reference to Visiblox
Charts and try it out. As ever, if you have any thoughts,
feedback or improvements, drop us a line.