Blog

Animating Visiblox Charts

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.

Post a comment