This example demonstrates some of the different ways in which Visiblox Charts can be customised.
You can retemplate the chart, add animations, change the line stroke to a gradient, change the axis' format string and much more.
Note that this example uses data from an external data source not included in the code snippets - download the examples source code in full to view the data.
XAML + CODE +
<UserControl x:Class="Visiblox.Charts.Examples.Premium.CustomisedTemplates.CustomisedTemplatesExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:charts="clr-namespace:Visiblox.Charts;assembly=Visiblox.Charts"
xmlns:primitives="clr-namespace:Visiblox.Charts.Primitives;assembly=Visiblox.Charts"
xmlns:local="clr-namespace:Visiblox.Charts.Examples.Premium.CustomisedTemplates"
mc:Ignorable="d">
<UserControl.Resources>
<Style x:Key="NoBorder" TargetType="Border">
<Setter Property="BorderThickness" Value="0" />
</Style>
<!-- Set the styles of the annotations -->
<Style x:Key="EventAnnotationStyle" TargetType="charts:TextAnnotationControl">
<Setter Property="Background" Value="LightGoldenrodYellow" />
</Style>
<Style x:Key="EventAnnotationHighlightedStyle" TargetType="charts:TextAnnotationHighlightElement" />
<charts:DateTimeRange x:Key="xAxisRange" Minimum="1/1/0001" Maximum="1/1/2150" />
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0" x:Key="GradientLineStroke">
<GradientStop Color="Green" Offset="0.0" />
<GradientStop Color="Green" Offset="0.5" />
<GradientStop Color="Red" Offset="1.0" />
</LinearGradientBrush>
<local:DateToYearConverter x:Key="DateToYearConverter" />
<ControlTemplate x:Key="ChartTemplate" TargetType="charts:Chart">
<Grid x:Name="LayoutRoot" Background="{TemplateBinding Background}" Opacity="{TemplateBinding Opacity}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock x:Name="TitleArea" Text="{TemplateBinding Title}" Grid.Row="0" Grid.Column="0" Style="{TemplateBinding TitleStyle}" />
<primitives:LegendLayoutContainer Grid.Row="1"
LegendPosition="{Binding Path=LegendPosition, RelativeSource={RelativeSource TemplatedParent}}">
<primitives:LegendLayoutContainer.XAxisPrimaryContainer>
<Grid x:Name="XAxisPrimaryContainer"/>
</primitives:LegendLayoutContainer.XAxisPrimaryContainer>
<primitives:LegendLayoutContainer.YAxisPrimaryContainer>
<Grid x:Name="YAxisPrimaryContainer"/>
</primitives:LegendLayoutContainer.YAxisPrimaryContainer>
<primitives:LegendLayoutContainer.XAxisSecondaryContainer>
<Grid x:Name="XAxisSecondaryContainer"/>
</primitives:LegendLayoutContainer.XAxisSecondaryContainer>
<primitives:LegendLayoutContainer.YAxisSecondaryContainer>
<Grid x:Name="YAxisSecondaryContainer"/>
</primitives:LegendLayoutContainer.YAxisSecondaryContainer>
<primitives:LegendLayoutContainer.ChartContent>
<Grid Name="PlotArea"
primitives:Clip.ToBounds="True" Style="{TemplateBinding PlotAreaStyle}">
<local:QuoteBackground />
<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>
<primitives:LegendLayoutContainer.LegendContent>
<charts:Legend x:Name="Legend" Title="{TemplateBinding LegendTitle}"
Style="{TemplateBinding LegendStyle}" Template="{TemplateBinding LegendTemplate}"
Visibility="{TemplateBinding LegendVisibility}" Ordering="{TemplateBinding LegendOrdering}"
/>
</primitives:LegendLayoutContainer.LegendContent>
</primitives:LegendLayoutContainer>
</Grid>
</ControlTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Define the chart -->
<!-- Ultimate Trial users should add 'ValidationKey="ENTER TRIAL LICENSE KEY HERE"' to each Chart declaration. -->
<charts:Chart Name="CustomisedTemplatesChart" Width="550" Height="350" Title="Estimated World Population At Various Dates"
LegendVisibility="Collapsed" PlotAreaBorderStyle="{StaticResource NoBorder}"
HorizontalAlignment="Center" Loaded="CustomisedTemplatesChart_Loaded" Grid.Column="1"
Template="{StaticResource ChartTemplate}">
<!-- Add zooming and Trackball Behaviours -->
<charts:Chart.Behaviour>
<charts:TrackballBehaviour />
</charts:Chart.Behaviour>
<!-- Define X axis (Y axis defined in code behind) -->
<charts:Chart.XAxis>
<charts:DateTimeAxis Title="Year" ShowMajorGridlines="False" ShowMinorTicks="False" Range="{StaticResource xAxisRange}" />
</charts:Chart.XAxis>
<!-- Define the data series -->
<charts:Chart.Series>
<charts:LineSeries LineStrokeThickness="2" ShowPoints="False" LineStroke="{StaticResource GradientLineStroke}" />
</charts:Chart.Series>
</charts:Chart>
<!-- Display the current trackball data -->
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3">
<TextBlock FontWeight="Bold" Text="Year: " />
<TextBlock Text="{Binding ElementName=CustomisedTemplatesChart, Path=Behaviour.CurrentPoints[0].X, Converter={StaticResource DateToYearConverter}}" />
<TextBlock FontWeight="Bold" Margin="30,0,0,0" Text="Estimated Population: " />
<TextBlock Text="{Binding ElementName=CustomisedTemplatesChart, Path=Behaviour.CurrentPoints[0].Y}" />
<TextBlock Text=" million" />
</StackPanel>
<!-- Controls that allow the user to change the base of the logarithmic axis -->
<StackPanel Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="3">
<TextBlock FontWeight="Bold" Text="Axis Base" />
<ListBox x:Name="YAxisBaseList" BorderThickness="0" Background="{x:Null}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem IsSelected="{Binding IsChecked, ElementName=Base2, Mode=TwoWay}" Margin="5,0,0,0">
<RadioButton x:Name="Base2" GroupName="BaseValue">2</RadioButton>
</ListBoxItem>
<ListBoxItem IsSelected="{Binding IsChecked, ElementName=Base3, Mode=TwoWay}" Margin="5,0,0,0">
<RadioButton x:Name="Base3" GroupName="BaseValue">3</RadioButton>
</ListBoxItem>
<ListBoxItem IsSelected="{Binding IsChecked, ElementName=Base5, Mode=TwoWay}" Margin="5,0,0,0">
<RadioButton x:Name="Base5" GroupName="BaseValue" IsChecked="True">5</RadioButton>
</ListBoxItem>
<ListBoxItem IsSelected="{Binding IsChecked, ElementName=Base10, Mode=TwoWay}" Margin="5,0,0,0">
<RadioButton x:Name="Base10" GroupName="BaseValue">10</RadioButton>
</ListBoxItem>
</ListBox>
</StackPanel>
</Grid>
</UserControl>
^ Back To Top
using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
namespace Visiblox.Charts.Examples.Premium.CustomisedTemplates
{
/// <summary>
/// An example chart using a logarithmic axis with annotations
/// </summary>
public partial class CustomisedTemplatesExample : UserControl
{
public CustomisedTemplatesExample()
{
InitializeComponent();
// Generate data points for the series
CustomisedTemplatesChart.Series.First().DataSeries = GenerateDataSeries();
}
/// <summary>
/// Load the data from the CSV file and return it as a data series
/// </summary>
/// <returns>The data series</returns>
private IDataSeries GenerateDataSeries()
{
var series = new DataSeries<DateTime, double>();
using (StreamReader reader = new StreamReader(ExampleHelpers.GetApplicationResourceStream("CustomisedTemplates/Data/Population.csv").Stream))
{
// First line is head information, skip before reading the data
reader.ReadLine();
// Read the points into the data series
while (reader.Peek() >= 0)
{
string line = reader.ReadLine();
string[] values = line.Split(',');
DateTime xValue = new DateTime(Int16.Parse(values[0]), 1, 1);
var dataPoint = new DataPoint<DateTime, double>(xValue, Double.Parse(values[1]));
series.Add(dataPoint);
}
}
return series;
}
private void CustomisedTemplatesChart_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
// Add the annotations once the chart has loaded
foreach (DataPoint<DateTime, double> dataPoint in CustomisedTemplatesChart.Series.First().DataSeries)
{
AddAnnotations(dataPoint);
}
// Define Y Axis
Charts.LogarithmicAxis yAxis = new Charts.LogarithmicAxis()
{
Title = "Estimated Population (in millions)",
ShowMajorGridlines = true,
ShowMinorGridlines = true,
ShowMinorTicks = false
};
// Set the Logarithmic Base property of the Y Axis to be bound to the selected radio button
yAxis.SetBinding(Charts.LogarithmicAxis.LogarithmicBaseProperty,
new Binding()
{
Source = YAxisBaseList,
Path = new PropertyPath("SelectedItem.Content.Content"),
Mode = BindingMode.TwoWay
});
CustomisedTemplatesChart.YAxis = yAxis;
}
private void AddAnnotations(DataPoint<DateTime, double> dataPoint)
{
string label = String.Empty;
string eventDescription = String.Empty;
double animationXOffset = 0.0;
// check to see if dataPoint is a significant event
switch (dataPoint.X.Year)
{
case 476:
eventDescription = "Western Roman Empire collapsed.";
break;
case 841:
eventDescription = "Dublin is founded by the Vikings";
break;
case 1066:
eventDescription = "Battle of Hastings";
break;
case 1350:
eventDescription = "Start of the Renaissance.";
break;
case 1492:
eventDescription = "Christopher Columbus discovers America.";
animationXOffset = 110;
break;
case 1636:
eventDescription = "Harvard University is founded.";
animationXOffset = 90;
break;
case 1912:
eventDescription = "Sinking of the Titanic.";
animationXOffset = 110;
break;
case 1950:
eventDescription = "First organ transplant.";
animationXOffset = 120;
break;
case 1969:
eventDescription = "Man lands on the moon.";
animationXOffset = 135;
break;
case 1989:
eventDescription = "The fall of the Berlin Wall.";
animationXOffset = 145;
break;
default:
break;
}
// create annotation if dataPoint is a significant event
if (!eventDescription.Equals(String.Empty))
{
label = (CustomisedTemplatesChart.Annotations.Count + 1).ToString();
// create label
EventInformation eventInformation = new EventInformation();
eventInformation.Label = label;
eventInformation.EventDescription = String.Format("{0} - ", dataPoint.X.Year.ToString()) + eventDescription;
eventInformation.AnimationXOffset = animationXOffset;
// create marker
TextAnnotation marker = new TextAnnotation(dataPoint, label);
marker.MouseEnter += new System.Windows.Input.MouseEventHandler(marker_MouseEnter);
marker.MouseLeave += new System.Windows.Input.MouseEventHandler(marker_MouseLeave);
marker.Tag = eventInformation;
marker.AnnotationElementStyle = this.Resources["EventAnnotationStyle"] as Style;
marker.MovingElementStyle = this.Resources["EventAnnotationHighlightedStyle"] as Style;
AddAnnotation(marker, true);
}
}
private void AddAnnotation(IAnnotation annotation, bool interactionEnabled)
{
// ensure the annotation axes correspond to the chart axes before adding them
// to the annotations collection
annotation.IsInteractionEnabled = interactionEnabled;
annotation.XAxis = CustomisedTemplatesChart.XAxis;
annotation.YAxis = CustomisedTemplatesChart.YAxis;
CustomisedTemplatesChart.Annotations.Add(annotation);
}
void marker_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
TextAnnotation activeAnnotation = (TextAnnotation)sender;
if (activeAnnotation == null)
return;
// force annotation to be displayed over other animations
CustomisedTemplatesChart.Annotations.Remove(activeAnnotation);
CustomisedTemplatesChart.Annotations.Add(activeAnnotation);
EventInformation eventInformation = ((EventInformation)activeAnnotation.Tag);
AnimateAnnotationTextChange(activeAnnotation, eventInformation.EventDescription, 0, -eventInformation.AnimationXOffset);
}
void marker_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
TextAnnotation activeAnnotation = (TextAnnotation)sender;
if (activeAnnotation == null)
return;
EventInformation eventInformation = ((EventInformation)activeAnnotation.Tag);
// Calculate how much the annotation needs to be moved based on its text
double startingPosition = activeAnnotation.Text.Equals(eventInformation.EventDescription) ? -eventInformation.AnimationXOffset : 0;
AnimateAnnotationTextChange(activeAnnotation, eventInformation.Label, startingPosition, 0);
}
/// <summary>
/// Animates a text change of an annotation.
/// </summary>
/// <remarks>
/// The animation if performed in several steps. Firstly <paramref name="annotation"/> is faded out. Whilst <paramref name="annotation"/> is not
/// visible it's text is changed to <paramref name="textValue"/> and is repositioned according to <paramref name="xStart"/> and
/// <paramref name="xShift"/>. Finally <paramref name="annotation"/> is faded back in.
/// </remarks>
/// <param name="annotation">The annotation to animate the text change for</param>
/// <param name="textValue">The text to be displayed by the annotation</param>
/// <param name="xStart">The x position relative to the annotations x position from which to start the animation</param>
/// <param name="xShift">The amount to shift the annotation on the x axis</param>
private void AnimateAnnotationTextChange(TextAnnotation annotation, string textValue, double xStart, double xShift)
{
Storyboard storyboard = new Storyboard();
const double animationDuration = 600;
// animationCriticalPoint is when the annotation is not visible - it is at this point
// when the text should be changed and the position updated.
const double animationCriticalPoint = animationDuration / 2;
// Create animation to change the text and the position at the correct time
ObjectAnimationUsingKeyFrames textAmimation = new ObjectAnimationUsingKeyFrames();
textAmimation.Duration = new Duration(TimeSpan.FromMilliseconds(animationDuration));
storyboard.Children.Add(textAmimation);
Storyboard.SetTarget(textAmimation, annotation);
Storyboard.SetTargetProperty(textAmimation, new PropertyPath("Text"));
DiscreteObjectKeyFrame textStage = new DiscreteObjectKeyFrame();
textStage.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(animationCriticalPoint));
textStage.Value = textValue;
textAmimation.KeyFrames.Add(textStage);
// Create animation to position the annotation correctly
DoubleAnimationUsingKeyFrames positionAnimation = new DoubleAnimationUsingKeyFrames();
positionAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(animationCriticalPoint));
annotation.RenderTransform = new TranslateTransform();
storyboard.Children.Add(positionAnimation);
Storyboard.SetTarget(positionAnimation, annotation);
Storyboard.SetTargetProperty(positionAnimation, new PropertyPath("(Rectangle.RenderTransform).(TranslateTransform.X)"));
DiscreteDoubleKeyFrame positionStage1 = new DiscreteDoubleKeyFrame();
positionStage1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0));
positionStage1.Value = xStart;
positionAnimation.KeyFrames.Add(positionStage1);
DiscreteDoubleKeyFrame positionStage2 = new DiscreteDoubleKeyFrame();
positionStage2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(animationCriticalPoint));
positionStage2.Value = xShift;
positionAnimation.KeyFrames.Add(positionStage2);
// Create a 2 phase animation to fade the annotation out and back in
DoubleAnimationUsingKeyFrames opacityAnimation = new DoubleAnimationUsingKeyFrames();
opacityAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(animationDuration));
storyboard.Children.Add(opacityAnimation);
Storyboard.SetTarget(opacityAnimation, annotation);
Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("Opacity"));
LinearDoubleKeyFrame fadeOutPhase = new LinearDoubleKeyFrame();
fadeOutPhase.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(animationCriticalPoint));
fadeOutPhase.Value = 0;
opacityAnimation.KeyFrames.Add(fadeOutPhase);
LinearDoubleKeyFrame fadeInPhase = new LinearDoubleKeyFrame();
fadeInPhase.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(animationDuration));
fadeInPhase.Value = 1;
opacityAnimation.KeyFrames.Add(fadeInPhase);
storyboard.Begin();
}
}
public class EventInformation
{
public String Label { get; set; }
public String EventDescription { get; set; }
public double AnimationXOffset { get; set; }
}
/// <summary>
/// A class which is used as the background of the chart,
/// </summary>
public class QuoteBackground : Canvas
{
public QuoteBackground()
{
Init();
this.SizeChanged += new SizeChangedEventHandler(QuoteBackground_SizeChanged);
}
void QuoteBackground_SizeChanged(object sender, SizeChangedEventArgs e)
{
Init();
}
private void Init()
{
Children.Clear();
Children.Add(CreateQuotesPanel());
}
private UIElement CreateQuotesPanel()
{
Border border = new Border();
border.Background = new SolidColorBrush(Color.FromArgb(255,250,250,210));
border.BorderThickness = new Thickness(1);
border.BorderBrush = new SolidColorBrush(Colors.LightGray);
border.Margin = new Thickness(10);
StackPanel quotesPanel = new StackPanel() { Orientation = Orientation.Horizontal };
quotesPanel.Margin = new Thickness(10);
quotesPanel.Children.Add(CreateQuoteImage());
quotesPanel.Children.Add(CreateQuoteText());
border.Child = quotesPanel;
return border;
}
private UIElement CreateQuoteText()
{
var quote = new TextBlock();
quote.Text = "\n\"The greatest shortcoming of the human race is our\n inability to understand the exponential function.\""
+ "\n\n - Albert Bartlett";
quote.FontStyle = FontStyles.Italic;
return quote;
}
private UIElement CreateQuoteImage()
{
var image = new Image();
image.Source = new BitmapImage(new Uri("a_bartlett.jpg", UriKind.Relative));
image.Margin = new Thickness(0, 0, 20, 0);
return image;
}
}
public class DateToYearConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value is DateTime)
{
return ((DateTime)value).Year;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
^ Back To Top

