Silverlight sparkline chart

According to Wikipedia

A sparkline is a very small line chart, typically drawn without axes or coordinates.

Though this chart is not included by default to the Silverlight Toolkit library, it is very easy to implement such chart by styling the built-in Line chart. Here is an example which I’ve implemented:

silverlight_sparkline_sample

At first I’ve changed the default control template of the Chart control, I’ve removed all paddings, the chart title, and reduced the minimum height and width of the chart. Also I’ve changed the DataPoint template (made them invisible) and the Polyline template (reduced its thickness). Here is the XAML code:

<UserControl.Resources>
    <Style x:Key="ChartWithoutPaddings" TargetType="chart:Chart">
        <Setter Property="Padding" Value="0" />
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="ChartAreaStyle">
            <Setter.Value>
                <Style TargetType="Panel">
                    <Setter Property="MinWidth" Value="100" />
                    <Setter Property="MinHeight" Value="20" />
                </Style>
            </Setter.Value>
        </Setter>
        <Setter Property="PlotAreaStyle">
            <Setter.Value>
                <Style TargetType="Grid">
                    <Setter Property="Background" Value="Transparent" />
                </Style>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="chart:Chart">
                    <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
                        <chartingprimitives:EdgePanel x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
                            <Grid Canvas.ZIndex="-1" Style="{TemplateBinding PlotAreaStyle}" />
                        </chartingprimitives:EdgePanel>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="EmptyDataPoint" TargetType="Control">
        <Setter Property="Background" Value="Black" />
        <Setter Property="Template" Value="{x:Null}" />
    </Style>

    <Style x:Key="OnePixelLine" TargetType="Polyline">
        <Setter Property="StrokeThickness" Value="1" />
    </Style>
</UserControl.Resources>

This code is almost all that you need to create a sparkline chart. All that is left is to remove axes. It wasn’t a trivial thing, so I used some kind of a hack: I set their width (for Y axis) and height (for X axis) to zero.

<chart:Chart Style="{StaticResource ChartWithoutPaddings}">
    <chart:LineSeries ItemsSource="{Binding FirstIndexItems}" IndependentValuePath="Number" DependentValuePath="Value" 
                        DataPointStyle="{StaticResource EmptyDataPoint}" 
                        PolylineStyle="{StaticResource OnePixelLine}"  />
    <chart:Chart.Axes>
        <chart:LinearAxis Orientation="X" Height="0" Opacity="0" />
        <chart:LinearAxis Orientation="Y" Width="0" Opacity="0" />
    </chart:Chart.Axes>
</chart:Chart>

Links
Source code: SparklineChartSample.zip
Showcase: SparklineChartSampleTestPage.html

Silverlight and WP7 chart with data point labels

Recently I found the question on stackoverflow in which one user asked how to add labels to data points on WPF/Silverlight/Windows Phone Toolkit charts. Whereas almost every javascript chart can do this, this functionality is absent in Silverlight charts and none of developers cares.

So I wrote a simple class which extended the LinearSeries class and now line charts can be displayed like this:

silverlight_chart_datapointlabels

wp7_chart_datapointlabels

I added labels only for line charts, but if someone needs them for column charts, I can try to implement it as well.
In the current implementation I created the class which inherits the LineSeries class. It has the following properties:
DisplayLabels – You should set it to true explicitly so that labels are displayed. It is false by default.
LabelBindingPath – Optional. You can specify a custom property of your model which will be displayed instead of the value from the Y-axis.
LabelStyle – Optional. You can change foreground, font weight, font size and other properties of labels.

The example of usage:

<chart:Chart Width="600" Height="300">
    <local:ExtendedLineSeries
        ItemsSource="{Binding Items}"
        DependentValuePath="ItemValue"
        IndependentValuePath="Title" 
        DisplayLabels="True"
        LabelBindingPath="ItemValue" />
</chart:Chart>

The complete source code of the class:

public class ExtendedLineSeries : LineSeries
{
    private Canvas _labelsCanvas;
    private Dictionary<DataPoint, TextBlock> _currentLabels = new Dictionary<DataPoint, TextBlock>();

    /// <summary>
    /// Gets or sets a value indicating whether labels should be displayed. 
    /// </summary>
    public bool DisplayLabels { get; set; }

    /// <summary>
    /// Gets or sets the binding path of the label.
    /// </summary>
    public string LabelBindingPath { get; set; }

    /// <summary>
    /// Gets or sets the style of each label.
    /// </summary>
    public Style LabelStyle { get; set; }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        // get a canvas to which the labels will be added
        this._labelsCanvas = (Canvas)this.GetTemplateChild("PlotArea");
        // clear the clip property so that labels are visible even if they exceed the bounds of the chart
        this.Clip = null;
    }

    protected override void UpdateDataPoint(DataPoint dataPoint)
    {
        base.UpdateDataPoint(dataPoint);

        // after the data point is created and added to the chart, we can add a label near it
        if (this.DisplayLabels && dataPoint.Visibility == System.Windows.Visibility.Visible)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() => this.CreateLabel(dataPoint));
        }
    }

    private void CreateLabel(DataPoint dataPoint)
    {
        // this method is also called with the SizeChanged event, so I create the label only one time
        TextBlock label;
        if (this._currentLabels.ContainsKey(dataPoint))
        {
            label = this._currentLabels[dataPoint];
        }
        else
        {
            label = new TextBlock();
            this._labelsCanvas.Children.Add(label);
            this._currentLabels.Add(dataPoint, label);

            label.Style = this.LabelStyle;

            // bind the label text to the specified path, or to dataPoint.DependantValue by default
            Binding binding = this.LabelBindingPath != null
                        ? new Binding(this.LabelBindingPath) { Source = dataPoint.DataContext }
                        : new Binding("DependentValue") { Source = dataPoint };
            BindingOperations.SetBinding(label, TextBlock.TextProperty, binding);
        }

        // calculate a position of the label
        double coordinateY = Canvas.GetTop(dataPoint) - label.ActualHeight; // position the label above the data point
        double coordinateX = Canvas.GetLeft(dataPoint) + dataPoint.ActualHeight / 2 - label.ActualWidth / 2; // center horizontally
        Canvas.SetTop(label, coordinateY);
        Canvas.SetLeft(label, coordinateX);
    }
}

In this code I made an override of the UpdateDataPoint method and each time a DataPoint is updated I update its label.
The code work with Silverlight as well as with Windows Phone 7. For WP7 I used the recompiled library which I published in one of my previous posts.

Source code of the sample application: ChartPointLabelsSample.zip
Show case: ChartPointLabelsSampleTestPage.html

Windows Phone 7 chart libraries overview. AmCharts

After the previous post I decided to explore the internet and find some other chart libraries for WP7. Unfortunately, there are only two free chart libraries: the previously mentioned Silverlight Toolkit and AmCharts. You can download the last charts here: http://wpf.amcharts.com/download.

This is a very small library and the last time it was updated was more than 1 year ago. It contains only 4 charts: Line, Column, Area, Pie. Here are screenshots of them:

The code for a chart item is quite simple:

<amq:SerialChart Style="{StaticResource chartStyle}" 
		DataSource="{Binding Charts[0].Items}" 
		CategoryValueMemberPath="Month">
	<amq:SerialChart.Graphs>
		<amq:LineGraph ValueMemberPath="Value" Title="Test Chart" />
	</amq:SerialChart.Graphs>
</amq:SerialChart>

For some unknown reason, the pie chart should be created by the different XAML code:

<amq:PieChart DataSource="{Binding Charts[3].Items}"
		TitleMemberPath="Month" 
		ValueMemberPath="Value" />

Advantages of these charts:
1. Free and open source (source code: https://github.com/ailon/amCharts-Quick-Charts);
2. Display the tooltip if you tap a data series;
3. Look better on the phone than the Toolkit charts with the default template.
4. Have small size and must work faster than Toolkit.

Disadvantages:
1. Only 4 kinds of charts;
2. Almost no customization, difficult to apply your own design;
3. Annoying behavior: if you tap the legend or the y-axis they will disappear and you will have to tap once again to display them;

As for me, I would use the Toolkit library and would customize their theme so that it looks more like metro style controls. But this library is good as well if you want just to display a chart without any customization.

The source code of the sample application with AmCharts you can download here: Wp7ChartsSamples.Amcharts.zip

Windows Phone 7 charts. Silverlight Toolkit.

Earlier I thought that WP7 Toolkit had chart controls. But it was surprising for me that charts weren’t ported to the WP7 library and if you want to use them, you have to use the Silverlight 3 toolkit with the S3 controls library as it is described in this post. The thing that I don’t like is that I have to add an extra library and also there is the annoying warning message that the library isn’t compatible with WP7. So I created the WP7 library, downloaded the source code of the toolkit and rewrote some code so that it didn’t use the extra reference.

Now you can use this library, which is more suitable for WP7: System.Windows.Controls.DataVisualization.Toolkit.dll.
For some reason the size of this library is 400kb, slightly larger than the silverlight library, I think it is because I haven’t removed *.xaml files.

As it was already said, the xaml code is the same as in WPF and Silverlight versions:

<charting:Chart>
	<charting:PieSeries Title="Test items" ItemsSource="{Binding Items}" DependentValuePath="ItemValue" IndependentValuePath="Title" />
</charting:Chart>

I’m going to examine some other chart libraries for Windows Phone 7 and I will write about their advantages and disadvantages in future posts.

The source code of the tooolkit charts and the sample application you can download here: WP7ToolkitCharts.zip

Silverlight Chart with reversed Y axis

Recently on StackOverflow.com I’ve found a question about Silverlight charts:
“Is it possible to display a Y LinearAxis in Silverlight in reversed order? Instead of lower numbers at the bottom increasing to the top of the Y axis, I would like to see lower numbers at the top”.

I can’t imagine where such chart can be used, maybe to display a bad revenue report in the best possible way. The chart on the right looks much better than the left one.

two linear charts comparison

Anyway after lot of efforts I’ve managed to create such axis without changing the source code of the Toolkit library, just by using the inheritance so that you can use it so:

<chart:Chart>
	<cr:ReversedAxisColumnSeries ItemsSource="{Binding Items}" DependentValuePath="Value" IndependentValuePath="Title" />
	<cr:ReversedAxisLineSeries ItemsSource="{Binding Items}" DependentValuePath="Value" IndependentValuePath="Title" />
	<chart:Chart.Axes>
		<cr:ReversedLinearAxis Orientation="Y" />
	</chart:Chart.Axes>
</chart:Chart>

Here is links to the solution and inside the post I’ll describe how I’ve implemented these classes.
Source code: ChartReversedAxisSample.zip
Showcase: TestPage.html
Read more of this post

Silverlight chart with a Goal line

Some time ago I needed a chart with a horizontal line describing some desirable value and visually demonstrating a relation between a planned value and actual values. Here is the result:

I tried to handle all possible situations and charts.
A case when the goal value (90) is much higher then the highest value:

A case when the goal value (-20) is lower then 0:

A case when there are both positive and negative values:

The code of the extended chart is rather difficult to explain, so use it as it is.
But I can explain usage of this chart. Here is the code example:

<UserControl.Resources>
    <Style x:Key="ChartGoalLineStyle" TargetType="Polyline" >
        <Setter Property="StrokeThickness" Value="1" />
        <Setter Property="Stroke" Value="#FF27AAE1" />
    </Style>
</UserControl.Resources>
<local:ExtendedChart GoalValue="35" GoalLineOrientation="X" GoalLineStyle="{StaticResource ChartGoalLineStyle}">
    <chart:LineSeries ItemsSource="{Binding Items}" DependentValuePath="Value" IndependentValuePath="Month" />
</local:ExtendedChart>

So there are 3 mandatory properties:
1. GoalValue – the value of the goal in relation to data values. If the data is measured in millions – goal value should be in millions too, if the data is in percents – the goal value should be between 0 and 100. But it is not required, as you have already seen, the goal value can be as well very high or lower then 0.
2. GoalLineOrientation – This value should be “X” for Line,Column,Area charts and “Y” for Bar charts. Must be parallel to an independent non-value axis. I tried to detect preferable orientation automatically, but it is not so easy and can lead to many bugs.
3. GoalLineStyle – Any style which can be applied to the Polyline class. In my example I’ve set only the color and thickness, but it is also possible to make the line dashed, for example.

Optional properties (not required):
1. GoalRectangleStyle – The background between the line and zero-point. you can change its color, borders and so on.
2. IsLegendDisplayed – Some charts look better without the legend, so I added this important option.

Here is source code of the solution: SilverlightChartWithGoalLine.zip
Showcase: SilverlightChartWithGoalLineTestPage.html

Silverlight TimeLine Chart

In this article I will create the chart which looks like the google timeline control. Here is the screenshot of the google chart which is displayed on this page:

My implementation isn’t exactly the same, but is very close:

  • it allows to move left and right;
  • it displays a line on the mouse over event and set a line if a user clicks somewhere;
  • this chart binds a selected value or a mouse over value to the closest item of the chart;
  • the chart is scrolled automatically if a user clicks at some distance from the middle of the chart.

In my implementation I’ve decided to use combination of the user control and the custom control.
The custom control has the following responsibilities:

  • displays a line if the mouse is over the chart
  • displays another line if a user clicks on the chart
  • provides two properties: MouseOverValue and ClickValue

The user control and its View Model provide operations with data, such as changing the range and binding the selected point to the nearest data value.

The code files are too large to paste them here, so I will explain only the User Control:

<Button Content="&lt;" Width="{StaticResource ButtonSize}"
		Height="{StaticResource ButtonSize}"
		Command="{Binding MoveLeftCommand}" Margin="5" Grid.Row="1" />
<local:ExtendedChart x:Name="chart" Grid.Column="1" Grid.Row="1"
		MouseOverValue="{Binding MouseOverDate, Mode=TwoWay}"
		ClickValue="{Binding SelectedDate, Mode=TwoWay}">
  <local:ExtendedChart.Series>
    <local:ExtendedColumnSeries ItemsSource="{Binding Items}"
			DependentValuePath="Value" IndependentValuePath="Date"
			DataPointStyle="{StaticResource SimpleColumnStyle}" />
  </local:ExtendedChart.Series>
  <local:ExtendedChart.Axes>
    <Charting:DateTimeAxis Orientation="X" IntervalType="Hours"
			Interval="6" Minimum="{Binding StartDate}"
			Maximum="{Binding EndDate}" />
  </local:ExtendedChart.Axes>
</local:ExtendedChart>
<Button Content="&gt;" Grid.Column="2"
		Width="{StaticResource ButtonSize}"
		Height="{StaticResource ButtonSize}"
		Command="{Binding MoveRightCommand}" Margin="5" Grid.Row="1" />

There are two buttons and one chart.

The buttons scroll the visible content left or right, they are square with some constant size and are bound to the commands of the View Model.

The chart has two bound properties (and these properties have two way binding because they will be updated in the View Model), one column series and one custom Axis.
I have extended the default column series because it has spaces between columns, also I’ve applied custom data point style to the series in which I’ve removed borders and have left only blue rectangle.
The axis displays ticks and labels each 6 hours and is restricted by the range which is set in the View Model.

Here is the complete source code: TimeLineChart.zip

Showcase: TimeLineChartTestPage.html

Silverlight Chart: multiple series databinding

Recently I needed a chart with multiple series (legend items) which would be able to display data from an OLAP database, like this:

The standard Chart requires to define each legend item in XAML, which doesn’t satisfy me, because I don’t know the actual number of returned items from a data source. So I decided to extend this control. Fortunately, there was a little work: only 3 properties and 1 method.

And now I can write xaml in this way:

        <local:ExtendedChart ItemsSource="{Binding Series}">
            <local:ExtendedChart.ItemTemplate>
                <DataTemplate>
                    <chart:ColumnSeries ItemsSource="{Binding Items}" DependentValuePath="Value" IndependentValuePath="Month" Title="{Binding Title}" />
                </DataTemplate>
            </local:ExtendedChart.ItemTemplate>
        </local:ExtendedChart>

The ExtendedChart control doesn’t require to redefine its template and contains few lines of code:

    public class ExtendedChart : Chart
    {
        /// <summary>
        /// List of CLR-objects which represent series of the chart
        /// </summary>
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ExtendedChart), new PropertyMetadata(null, (s, e) => ((ExtendedChart)s).InitSeries()));

        /// <summary>
        /// Template for an item, transforms a CLR-object to an ISeries object
        /// </summary>
        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }

        public static readonly DependencyProperty ItemTemplateProperty =
            DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ExtendedChart), new PropertyMetadata(null, (s, e) => ((ExtendedChart)s).InitSeries()));

        /// <summary>
        /// This property is necessary for stacked charts
        /// </summary>
        public DataTemplate ItemsHostTemplate
        {
            get { return (DataTemplate)GetValue(ItemsHostTemplateProperty); }
            set { SetValue(ItemsHostTemplateProperty, value); }
        }

        public static readonly DependencyProperty ItemsHostTemplateProperty =
            DependencyProperty.Register("ItemsHostTemplate", typeof(DataTemplate), typeof(ExtendedChart), new PropertyMetadata(null, (s, e) => ((ExtendedChart)s).InitSeries()));

        private void InitSeries()
        {
            this.Series.Clear();
            if (this.ItemsSource == null || this.ItemTemplate == null)
                return;

            //From items to series
            var series = from item in this.ItemsSource.OfType<object>()
                        let seriesItem = this.ItemTemplate.LoadContent() as ISeries
                        where seriesItem != null && seriesItem is FrameworkElement
                        let dummy = ((FrameworkElement)seriesItem).DataContext = item
                        select seriesItem;

            //Generic series and stacked series are different, that's why I use this if-else
            var hostSeries = this.ItemsHostTemplate != null ? this.ItemsHostTemplate.LoadContent() as DefinitionSeries : null;
            if (hostSeries != null)
            {
                this.Series.Add(hostSeries);
                series.OfType<SeriesDefinition>().ToList().ForEach(hostSeries.SeriesDefinitions.Add);
            }
            else series.ToList().ForEach(this.Series.Add);
        }
    }

Here is the completed solution: BindableSilverlightChart.zip | Download from Google Docs
Show case: Test page

Follow

Get every new post delivered to your Inbox.