Posted by: beatkiener | February 22, 2010

How to make Visifire Chart bindable to ViewModel

The Visifire chart control is very cool and handy, but there is one missing part: it does not support data binding. The Visifire team confirmed that the current release (version 3.0.1.0) is not bindable and they are working on this issue with high priority: (http://www.visifire.com/forums/index.php?showtopic=1631).
Currently I’m working in a large Silverlight project where we use the Visifire chart and doing straight MVVM.

First attempt

In my first attempt I created a BindableDataSeries class inherited from DataSeries like the same concept I did to make the Silverlight Toolkit charts mulit-series bindable. See here: http://blog.thekieners.com/2010/02/07/databinding-multi-series-charts

I added a DataPointSource of type IEnumarable and DataPointTemplate of type DataTemplate to the BindableDataSeries class. Both properties are bindable. When the SeriesSource is set then DataSeries elements are generated based on the data template in the SeriesTemplate property in the same manner as an ItemsControl generates its items. Finally the generated DataSeries are added to the Series collection.

Now I’m able to bind the data point to my ViewModel as following:

    <vc:Chart>
        <vc:Chart.Series>
            <local:BindableDataSeries DataPointSource="{Binding SalesData}" >
                <local:VisifireBindableDataSeries.DataPointTemplate>
                    <DataTemplate>
                        <vc:DataPoint AxisXLabel="{Binding SalesName}"
                                      YValue="{Binding SalesPerformance}"/>
                    </DataTemplate>
                </local:VisifireBindableDataSeries.DataPointTemplate>
            </local:BindableDataSeries>
        </vc:Chart.Series>
    </vc:Chart>

Unfortunately there is still one problem. The DataPoint is an UIElement which is never part of the visual tree and binding gets not applied, because the binding is not executed when the element is not in the visual tree. As a result the DataPoint properties remain empty.

Second attempt

It shows to me that the only way to setup a Visifire Chart with data from a ViewModel is by writing code. How to do this without getting a strong reference between the View and the ViewModel? Remember to MVVM concepts, when creating a strong reference between View and ViewModel (either in one or both ways) you will lose many advantages the MVVM serves you.

In my second attempt I’ve created a Visifire Chart Wrapper which contains the most common properties of the Visifire Chart as bondable dependency properties.

Let me explain it step by step.

The control is inherited from System.Windows.Control and not from ContentControl, because there is no need for a composite control. The VisifireWrapper uses a static template originated from the code and not from the generic.xaml, because I don’t want to allow to re-template it. In the OnApplyTemplate override is a check implemented to guarantee this. Microsoft uses the same concept in the ViewBox control contained in the Silverlight Toolkit (https://silverlight.svn.codeplex.com/svn/Release/Silverlight3/Source/Controls.Toolkit/Viewbox/Viewbox.cs).

    /// <summary>
    /// VisifireWrapper serves as an Visifre Chart Wrapper to get the Chart MVVM bindable.
    /// You must inherit from this class an implement OnUpdateChart method.
    /// </summary>
    public abstract class VisifireWrapper : Control
    {

        public VisifireWrapper()
        {
          // Load the default template
          this.Template = DefaultTemplate = XamlReader.Load(DefaultTemplateMarkup) as ControlTemplate;
          ApplyTemplate();
        }

        /// <summary>
        /// XAML markup used to define the write-once wrapper template.
        /// </summary>
        private const string DefaultTemplateMarkup =
            "<ControlTemplate " +
            "    xmlns='http://schemas.microsoft.com/client/2007'  " +
            "    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'  " +
            "    xmlns:local='clr-namespace:VisifireMvvmIntegrationDemo;assembly=VisifireMvvmIntegrationDemo' " +
            "    TargetType='local:VisifireWrapper' > " +
            "    <ContentPresenter  " +
            "        Content='{TemplateBinding Chart}'  " +
            "        HorizontalAlignment='Stretch'  " +
            "        VerticalAlignment='Stretch'  /> " +
            "</ControlTemplate> ";

        /// <summary>
        /// Gets or sets the default ControlTemplate of the VisifireWrapper.
        /// </summary>
        private ControlTemplate DefaultTemplate { get; set; }

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

            // Ensure the Template property never changes from the
            // DefaultTemplate, and only apply it one time.
            if (Template != DefaultTemplate)
                throw new InvalidOperationException("The template can only be applied one time.");
        }
    }

 

The template contains a ContentPresenter which is data bound to the Chart property. The Chart property is set in the OnUpdateChart method which I’m going to introduce in a minute.

As next I added the most commonly used Visifire properties as dependency properties. Please find the full source code of the beneath dependency properties in the attached source.

/// <summary>
/// Gets or sets a collection used to generate the data point series.
/// </summary>
public IEnumerable ChartSource
{
    get { return (IEnumerable)GetValue(ChartSourceProperty); }
    set { SetValue(ChartSourceProperty, value); }
}

/// <summary>
/// Gets or sets a collection used to generate the chart multiline-title.
/// </summary>
public IEnumerable TitlesSource
{
    get { return (IEnumerable)GetValue(TitlesSourceProperty); }
    set { SetValue(TitlesSourceProperty, value); }
}

/// <summary>
/// Gets or sets a collection used to generate X-Axes
/// </summary>
public IEnumerable AxesXSource
{
    get { return (IEnumerable)GetValue(AxesXSourceProperty); }
    set { SetValue(AxesXSourceProperty, value); }
}

/// <summary>
/// Gets or sets a collection used to generate Y-Axes
/// </summary>
public IEnumerable AxesYSource
{
    get { return (IEnumerable)GetValue(AxesYSourceProperty); }
    set { SetValue(AxesYSourceProperty, value); }
} 

ChartSource will point to a collection of chart points or a collection of multi chart data. Multi chart data is meant to be used when you want to defined the numbers of lines in a line-chart dynamically. The attached demo project contains an example of this.

TitlesSource points to a collection of title information. Visifire Charts allow you to define a title with multiple lines. Each item in the collection is meant to be one line in the chart title.

AxesXSource and AxesYSource points to collections of specific axes-captions or axes-settings.

The VisifireWrapper contains abstract method OnUpdateChart. When overridden in sub class it updates the Visifire Chart wrapped by the VisifireWrapper control. The method is called in the first layout pass after one of the sources ChartSource, TitlesSource, AxesXSource or AxesYSource has changed. This is unlike the ItemsControl where the child items gets generated when the source changes. Recently I discovered that the DataGrid works exactly the same way: it generates its items (rows, etc) in the first layout pass after the ItemsSource has changed. But why I do it that way? The answer is performance, as you can read in the next section.

The charts are very nice animated during the first load (this is quite import for us, because it is part of a bigger aim to serve a new user experience to our customers. But this is another story…). Unfortunately the Visifire Chart does not allow updating DataPoints in every circumstance. Changing existing DataPoints is not a problem until you want to add or remove DataPoints. When you add DataPoints to an existing series the chart does this animated but the output is not correct as you can see in the print screen below:

Before…

clip_image001

…and after adding one DataPoint

clip_image002

Adding or removing DataPoints is not possible so far (version 3.0.1.0). The solution is to remove all DataPoints and re-add it to the chart. In that way we lose the animation, because the chart does animate only on first time load or when updating existing points. Therefore our solution is quite simple: every time any of the chart sources changes a new chart control instance is created with new DataPoints. OnUpdateChart override looks like this:

public class ColumnChart : VisifireWrapper
{
    protected override void OnUpdateChart()
    {

        // init chart control
        Chart chart = new Chart();
        chart.Theme = "Theme2";
        chart.AnimatedUpdate = true;
        chart.AnimationEnabled = true;
        chart.View3D = true;

        // create data series when DataSource is set
        if (this.ChartSource != null)
        {
            // init data point series
            DataSeries series = new DataSeries();
            series.RenderAs = RenderAs.Column;
            series.ShadowEnabled = true;
            series.XValueType = ChartValueTypes.Date;
            series.YValueFormatString = "hh:mm tt";
            series.SelectionEnabled = false;
            chart.Series.Add(series);

            foreach (PointData item in this.ChartSource)
            {
                DataPoint dataPoint = new DataPoint();
                dataPoint.XValue = item.Date;
                dataPoint.YValue = item.Value;
                series.DataPoints.Add(dataPoint);
            }
        }

        // set the chart title
        if (this.TitlesSource != null)
        {
            foreach (string title in this.TitlesSource)
            {
                Title chartTitle = new Title();
                chartTitle.Text = title;
                chart.Titles.Add(chartTitle);
            }
        }

        // completely replace the chart control
        this.Chart = chart;

    }

}

To set the values for your needs, the Visifire Chart samples provide excellent demos as XAML and its very simple to translate it into imperative code.

At the end of the method you must set the Chart property with the new chart. The new chart gets data bound to the ContentPresenter through TemplateBinding.

Internals

Let’s have a look into the internals of the abstract class VisifireWrapper.

Below is the implementation of the ChartSource dependency property, served as a model for the other properties:

/// <summary>
/// Gets or sets a collection used to generate the data point series.
/// </summary>
public IEnumerable ChartSource
{
    get { return (IEnumerable)GetValue(ChartSourceProperty); }
    set { SetValue(ChartSourceProperty, value); }
}
public static readonly DependencyProperty ChartSourceProperty =
    DependencyProperty.Register("ChartSource",
        typeof(IEnumerable),
        typeof(VisifireWrapper),
        new PropertyMetadata(new PropertyChangedCallback(OnChartSourceChanged)));

private static void OnChartSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    IEnumerable oldValue = (IEnumerable)e.OldValue;
    IEnumerable newValue = (IEnumerable)e.NewValue;
    VisifireWrapper source = (VisifireWrapper)d;
    source.OnChartSourceChanged(oldValue, newValue);
}

protected virtual void OnChartSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    _chartSourceCollectionChanged.SetEventSource(newValue);

    InvalidateChart();
}

 

In the changed handler are two things to do:

  • Set the event source for the WeakEventSource wrapper. The wrapper serves a weak event pattern to listen for CollectionChanged event in a non-memory-leaking way. In two previous post I explain in detail what the WeakEventSource and WeakEventListener serves you.
  • Secondly set an internal flag to invalidate the chart so it gets updated the next time the control renders its child.

You can easily implement additional dependency properties. When using the WeakEventSource you do not have to think about possible memory leaks when listen for source-events.

Summary

The VisifireWrapper is a very helpful base class to implement custom Visifire charts which are bindable to a ViewModel without having a strong reference between the View and ViewModel. The control supports the Silverlight content model except data-templating and encapsulates the weak-event-pattern. In addition the implementation shows the usage of the WeakEventSource implementation I did from a previous post.

Any feedbacks are welcome.

Demo & Source

Below you can find a demo project and the full source code of the VisifireWrapper and WeakEventSource. The demo contains a Single-Series and Multi-Series chart and a demo implementation for common chart types.

clip_image002

Advertisement

Responses

  1. Kiener,

    Visifire now supports databinding. http://www.visifire.com/blog/2010/02/16/visifire-now-supports-databinding/

  2. Visifire already started supporting DataBinding. Please have a look into the following release blog http://www.visifire.com/blog/2010/02/16/visifire-now-supports-databinding/

  3. Thank you for this link.

    I wrote this wrapper at the beginning of this year when we started evaluating Chart vendors for our Silverlight framework. Before I published it on my blog I noticed the news about new data binding capability in beta version 3.0.3. Unfortunately there are still some open points for us.

    Critical:

    -Binding multi series is still not possible. We have the case to dynamically add or remove lines in a multi-line chart.

    -The way the DataSource property is implemented produces a memory leak when the ViewModel longer lives as the view. Please consider the following three blog post:

    http://blog.thekieners.com/2010/02/17/weakeventsource-implementation-2/

    http://blog.thekieners.com/2010/02/11/simple-weak-event-listener-for-silverlight/

    And one from Davion Anson (Micosoft): http://blogs.msdn.com/delay/archive/2009/03/09/controls-are-like-diapers-you-don-t-want-a-leaky-one-implementing-the-weakevent-pattern-on-silverlight-with-the-weakeventlistener-class.aspx

    -Unlike the WPF/Silverlight binding engine, Visifire chart crashes when the binding is invalid. That means the application gets down.

    -We really like the animation when the chart loads the first time. With our workaround we have the animation for updates also.

    Not critical, but not in an expected manner

    -The path property isn’t really a path like the meaning in WPF/SL. It’s more like a source-property. I can’t define Path=”Person.Age”. Of course we can fill the data source as the chart it needs but it is not what we except from a path property.

    -I still miss the data template mechanism, but I think this will be hard to apply without a breaking change.

    I know, the bindable Chart version is still beta and I hope you and the Visifire team does not misunderstand my comments. We are still evaluating different chart vendors and Visifire is on our short list. I hope the Visifire team will pick up the critical points from above.

    Best regards,
    Beat Kiener

  4. Hi Kiener,

    Thanks for the feedback. We’ll consider the points that you have mentioned.

    Regards,
    Sunil Urs
    Team Visifire

  5. Hi Kiener,

    We have fixed all critical issues reported by you. Thanks for your valuable comments.

    [Binding multi series is still not possible. We have the case to dynamically add or remove lines in a multi-line chart]

    * Binding Multiseries was possible from the day one when DataBinding was implemented in Visifire. If you look into MS chart you can see that you need to specify series in DataTemplate for different types of Series. You need to specify Path for each bindings. But Visifire supports DataSeries which is generic for different chart types. If you want to bind multiseries chart you need to create one or more number of DataSeries, set binding definition for each DataSeries then you can set separate DataSource for each series. If you have only one DataSource you can set DataContext in Chart. Below is an example in Visifire documentation for multiseries DataBinding.

    http://visifire.com/documentation/Visifire_Documentation/Common_Tasks/DataBinding_in_a_multi-series_chart.htm

    [The way the DataSource property is implemented produces a memory leak when the ViewModel longer lives as the view. Please consider the following three blog post:
    http://blog.thekieners.com/2010/02/17/weakeventsource-implementation-2/
    http://blog.thekieners.com/2010/02/11/simple-weak-event-listener-for-silverlight/

    * Issues related to memory leak have been fixed now. Let us know if you still find any issue related to memory leak.

    [Unlike the WPF/Silverlight binding engine, Visifire chart crashes when the binding is invalid. That means the application gets down]

    * Visifire will throw exception if binding is not valid. We have fixed issue related to bindings. Let us know if you find any issue related to binding. It will be very helpful if you can provide few test cases.

    [The path property isn’t really a path like the meaning in WPF/SL. It’s more like a source-property. I can’t define Path=”Person.Age”. Of course we can fill the data source as the chart it needs but it is not what we except from a path property]

    * Now Visifire supports multilevel property path while DataBinding.

    [I still miss the data template mechanism, but I think this will be hard to apply without a breaking change]

    * We intentionally don’t support Template for other Visifire elements except Chart and ToolTip. We don’t want DataPoint and DataSeries to behave like a normal control. We don’t allow changing a standard column to something else.

    [I know, the bindable Chart version is still beta and I hope you and the Visifire team does not misunderstand my comments. We are still evaluating different chart vendors and Visifire is on our short list. I hope the Visifire team will pick up the critical points from above.]

    * We really appreciate your comments on Visifire. Your suggestions are always welcome. We have thousands of customers who are working with Visifire and love to work. Your suggestion helped us to improve Visifire. Thanks for your valuable comments.

    Regards,
    Somnath
    Team Visifire

    • Thank you for your feedback. Recently we bought a Visifire license and the first project with Visifire charts is online. Thank you for the great work!


Leave a Reply

Fill in your details below or click an icon to log in:

Gravatar
WordPress.com Logo

Please log in to WordPress.com to post a comment to your blog.

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Categories

Follow

Get every new post delivered to your Inbox.