It's that time of year again, the first snow has fallen (at
least in these parts of the world) and the shop windows are packed
with Christmas decorations. It's still business as usual at
Visiblox HQ...well for the most part. Some of the elves took a
little time out to provide our blog with a bit of Christmas spirit.
Playtime!
What does Christmas have to do with Visiblox Charts, you ask?
Check it out!
Instructions for use:
- Decorate your Christmas tree with candles and decorations
- If you don't like it or want to try again, select the Santa
hand, shake the tree and start again! (Note, all decorations are
made of a durable titanium alloy and so will not break or dent on
impact - just pick them back up and place them on the tree if
desired...yes, even the candles...)
Falling Snowflakes
All of the above example was done using standard features of
Visiblox Charts 2.1. If you're curious about how, I'll take you
through the implementation now.
First of all we set about putting falling snowflakes in the
background of the chart. Note that it's not just an animated gif or
something, it's actual silverlight controls falling. I found some
excellent code which was used as a basis for this functionality
here. In order to use that I created a SnowflakePanel class and
retemplated the chart to put a snowflake panel in the
background.
We won't reproduce the whole chart template here (the code is
available for download in
full here- note you'll need the premium version of Visiblox
Charts) but the relevant piece is this:
<primitives:LegendLayoutContainer.ChartContent>
<Grid Name="PlotArea" Background="Transparent" primitives:Clip.ToBounds="True" Style="{TemplateBinding PlotAreaStyle}">
<local:SnowfallPanel Background="{StaticResource NightSky}" />
<Border Name="PlotAreaBorder" Style="{TemplateBinding PlotAreaBorderStyle}" Canvas.ZIndex="120" />
<Grid Name="SeriesContainer" />
<Grid Name="GridlinesContainer" Canvas.ZIndex="-100" />
<Canvas Name="BehaviourContainer" />
<Grid Name="AnnotationsContainer" />
</Grid>
</primitives:LegendLayoutContainer.ChartContent>
At this point, the chart will render with a background of
falling snowflakes.
The Tree
The tree is made up of 2 standard Visiblox Charts LineSeries
with ShowArea set to true. We set the area fill and line colours to
green and brown respectively for the tree and trunk. After that
it's just a matter of shaping the tree with appropriate points
using standard DataSeries. In order to keep things simple we define
axes with explicit ranges from 0 to 10 for both X and Y.
We define the data points in the MainPage constructor in code
behind:
//these are the coordinates of the tree
_tree.Add(2, 1);
_tree.Add(4, 2.5);
_tree.Add(2.5, 2.5);
_tree.Add(4.3, 4);
_tree.Add(3, 4);
_tree.Add(5, 5.5);
_tree.Add(7, 4);
_tree.Add(5.7, 4);
_tree.Add(7.5, 2.5);
_tree.Add(6, 2.5);
_tree.Add(8, 1);
_tree.Add(2, 1);
//these are the coordinates of the trunk
_trunk.Add(4.5, 0);
_trunk.Add(4.5, 1);
_trunk.Add(5.5, 1);
_trunk.Add(5.5, 0);
_trunk.Add(4.5, 0);
//create a gradient brush to fill in the tree
LinearGradientBrush treeBrush = new LinearGradientBrush();
treeBrush.StartPoint = new Point(0.5,0);
treeBrush.EndPoint = new Point(0.5,1);
GradientStop lightGreen = new GradientStop();
lightGreen.Color = Color.FromArgb(255,0,255,0);
lightGreen.Offset = 0;
GradientStop darkGreen = new GradientStop();
darkGreen.Color = Color.FromArgb(255,0,100,0);
darkGreen.Offset = 1;
treeBrush.GradientStops.Add(lightGreen);
treeBrush.GradientStops.Add(darkGreen);
//create a series for the tree and trunk
LineSeries xmasTree = new LineSeries() {
DataSeries = _tree,
LineStrokeThickness = 3,
LineStroke = new SolidColorBrush(Colors.Green),
AreaFill = treeBrush,
ShowArea = true
};
LineSeries trunk = new LineSeries()
{
DataSeries = _trunk,
LineStrokeThickness = 3,
LineStroke = new SolidColorBrush(Color.FromArgb(255,122,52,5)),
AreaFill = new SolidColorBrush(Color.FromArgb(255, 122, 52, 5)),
ShowArea = true
};
XmasChart.Series.Add(xmasTree);
XmasChart.Series.Add(trunk);
At this point we have a Christmas tree, in a night sky with
gentle falling snowflakes. Lets get to the decorating!
The Decorations
The decorations are implemented using a special ImageAnnotation
class, which we borrowed from our previous post about
extending annotations. All we do is grab a few clip art images
and stick them in a list box with a data template by binding to a
collection of Decoration objects. On selection change we change
which decoration is attached to the AnnotationBehaviour, which we
attached to the chart in XAML. We also define a custom
AnnotationFactory so that the AnnotationBehaviour knows which
annotation type to create.
That works fine, but the annotations hang in mid-air if you
don't put them on the tree. That won't do!
Gravity Abides
To avoid the "hanging annotation" problem, we implement a
simulation of gravity. This is fairly complicated in code behind,
but mostly because Silverlight doesn't seem to offer much in the
way of geometry support. So there is a fair chunk of code in the
example to find the intersection between two lines and to determine
whether the annotation was placed inside the tree in the first
place. Again, we won't replicate all that code here, feel free to download the
source and have a look though. Once we detect that it is in
mid-air, and have determined how far to let it fall, we then make
it happen.
To make it fall we use a DoubleAnimation to modify the Y value
of the data point to which the annotation is attached. However,
there is a snag. The default implementation of DataPoint is not a
DependencyObject which is required by the Silverlight animation
framework to work. To get around that we borrow the notion of a
DataPointWrapper from our blog post on
animating Visiblox Charts.
We can then simply use that to animate the data point, which in
turn causes the annotation to update its position.
private static void AnimateAnnotation(DataPoint<IComparable, IComparable> dp, double toValue)
{
//calculate the time it takes for the decoration to reach its new height
double distance = (double)dp.Y - toValue;
long interval = 300;
//should always be true but just in case
//acceleration
double g = 0.00002;
if (distance >0) {
interval = (long)Math.Sqrt(distance/g);
}
Storyboard sb = new Storyboard();
DoubleAnimation anim = new DoubleAnimation() { To = toValue, EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseIn }, Duration = TimeSpan.FromMilliseconds(interval) };
DataPointWrapper dpw = new DataPointWrapper(dp);
Storyboard.SetTarget(anim, dpw);
Storyboard.SetTargetProperty(anim, new PropertyPath("YValue"));
sb.Children.Add(anim);
sb.Completed += Animation_Completed;
//add a reference to the datapoint wrapper to prevent garbage collection
animations[sb] = dpw;
sb.Begin();
}
The Hand of Santa
So that just leaves us with clearing the tree. To do that we add
a PanBehaviour to the chart with only X-axis pan enabled. When the
hand toggle button is selected, the PanBehaviour is enabled and the
AnnotationBehaviour is disabled. We then register an event listener
for X-axis range events. Whenever the X-axis Effective range
changes (which describes the visible portion of the chart) we
determine whether the pan is going in the same direction as last
time we saw it. If not, we count that as a change of direction. If
we see 3 or more changes of direction, we need to clear the
tree.
To do that we just iterate over the Chart.Annotations collection
and animate all of their positions to 0, thus making them fall to
the ground.
Summary
In this post we've created a Christmas tree on a chart. Clearly,
it's not a realistic charting requirement, but it's seasonal, it
was fun to do and it demonstrates just how flexible Visiblox Charts
really is! As always you can download the source for this
example here. Note that you will need to add a reference to the
premium edition of Visiblox
Charts to use this.
In the meantime, from everyone at Visiblox: Have a very Merry
Christmas and a Happy New Year!!!