How can I bind the XamWebChart to a SeriesCollection. I'm getting data from a service, in Xml format, containing the values and styles for few series' with several datapoints.
I created SeriesCollection in my ViewModel. Now how should I bind my view to make it use the SeriesCollection to draw graphs.
You will want to check this post to get you started:
https://www.infragistics.com/community/forums/f/ultimate-ui-for-wpf/38169/series-binding-in-xaml-custom-attached-property
Unfortunately there seems to be a problem with the forums this morning, so this post isn't currently accessible.
-Graham
Its back up. So you can check that out if you want. But the exampl linked is really more for if you want to create the series based on a template in Xaml. If you are already creating the series collection in your View Model have you tried just binding it to the chart?
I.E if the property holding the SeriesCollection on your DataContext is called Series. Have you tried
<XamWebChart Series="{Binding Series} ...
The chart doesn't currently tolerate being populated with a null Series collection, so, if using a binding, you have to make sure that what the chart is being bound to can always provide a non-null series collection. For example, if i define my view model as a static resource so that it is accessible at the time of the creation of the page, then it behaves correctly. You could also create a proxy object to bind to that either provided an empty series collection or the actual series collection when accessible. Here is a sample:
The xaml:
<UserControl x:Class="SilverlightApplication60.MainPage" 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" mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480" xmlns:igChart="clr-namespace:Infragistics.Silverlight.Chart;assembly=Infragistics.Silverlight.DataVisualization.Chart.v9.2" xmlns:local="clr-namespace:SilverlightApplication60"> <Grid x:Name="LayoutRoot"> <Grid.Resources> <local:PageViewModel x:Name="testVM" /> </Grid.Resources> <igChart:XamWebChart DataContext="{StaticResource testVM}" Series="{Binding DefinedSeries}"> </igChart:XamWebChart> </Grid> </UserControl>
And the code behind:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } } public class PageViewModel : INotifyPropertyChanged { public PageViewModel() { _definedSeries = new SeriesCollection(); CreateTestData(); } private void CreateTestData() { Series lineSeries = new Series(); lineSeries.ChartType = ChartType.Line; lineSeries.DataMapping = "Label = Label; Value = Value"; lineSeries.DataSource = new TestChartData(); Series columnSeries = new Series(); columnSeries.ChartType = ChartType.Column; columnSeries.DataMapping = "Label = Label; Value = Value"; columnSeries.DataSource = new TestChartData(); _definedSeries.Add(lineSeries); _definedSeries.Add(columnSeries); } private SeriesCollection _definedSeries; public SeriesCollection DefinedSeries { get { return _definedSeries; } set { _definedSeries = value; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("DefinedSeries")); } } } public event PropertyChangedEventHandler PropertyChanged; } public class TestChartData : ObservableCollection<TestChartDataItem> { public TestChartData() { Add(new TestChartDataItem { Label = "1", Value = 1 }); Add(new TestChartDataItem { Label = "2", Value = 2 }); Add(new TestChartDataItem { Label = "3", Value = 3 }); } } public class TestChartDataItem { public string Label { get; set; } public double Value { get; set; } }
I've also created a bug (30475) indicating that the chart should tolerate being bound to view model properties that are initially null or unreachable.You can check the status of the bug at any time here or by contacting Developer Support.-Graham
Hi Graham,
Your reply helped. But when I try to update the datacontext of the control later sometime, its not getting updated. Its always showing the initial data.
I have all the property change notifications in place.
In the code, when I get new data I updated the data context as,
ChartControl.DataContext = newVM;
Help me.
Are you sure you are changing the data context at the right level? In the example I posted, if I change the chart's DataContext the series are updated. Are you changing the data context above the element which you originally set it on? Do you have a sample you can provide that exhibits the behavior you are seeing?
The bug is in review, you can check its status at any point by contacting Developer Support and providing them the bug number 30475.
Does this problem exists in both DataVisualization and Silverlight XamWebCharts?
Your problem is that when you are setting the actual DataContext programmatically you are setting it at a higher level than you are setting it inside the user control. The DataContext set on the chart will take precedence over the UserControl's DataContext. You have to replace the DataContext at the same level or lower than it was originally set.
One way to resolve your problem would be to give the chart in your user control a name
e.g. x:Name="theChart"
and then instead of this line:
ucChart.DataContext = vmChart;
write
(ucChart.FindName("theChart") as FrameworkElement).DataContext = vmChart;
let me know how it goes.
Thank you very much Graham. That works like a charm. If possible, please keep me posted about the bug status.
Thank you.
Hi,
I'm trying to do the same thing for a XamDataChart, but the Series property is marked as read-only. How would one assign the series collection to the control fro mthe view model in this control?
Sorry for the delay, I've been out of the office for a while. Here's something that can should get you started. The gist is to use an attached property to synchronize your source viewmodel with the series that are populated in the chart.The viewmodels for the series are produce the concrete series by means of a DataTemplate you provide which should produce a series when loaded.Here's an example of the Xaml:
<Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ig:XamDataChart Name="xamDataChart1" > <ig:XamDataChart.Axes> <ig:CategoryXAxis x:Name="xAxis" ItemsSource="{Binding Item.Data}" Label="{}{Label}"/> <ig:NumericYAxis x:Name="yAxis" /> </ig:XamDataChart.Axes> <local:SeriesBinder.BindingInfo> <local:SeriesBindingInfo ItemsSource="{Binding}"> <local:SeriesBindingInfo.SeriesTemplate> <DataTemplate> <ig:LineSeries ItemsSource="{Binding Item.Data}" ValueMemberPath="{Binding Item.ValueMemberPath}" XAxis="{Binding Path=Owner.Axes[0]}" YAxis="{Binding Path=Owner.Axes[1]}" > </ig:LineSeries> </DataTemplate> </local:SeriesBindingInfo.SeriesTemplate> </local:SeriesBindingInfo> </local:SeriesBinder.BindingInfo> </ig:XamDataChart> <Button Grid.Row="1" Content="Click" Click="Button_Click"/> </Grid>
And here is the code behind to support this usage:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); DataContext = new TestSeries(); } private void Button_Click(object sender, RoutedEventArgs e) { (DataContext as TestSeries).Add( new TestSeriesItem()); } } public class TestSeries : ObservableCollection<TestSeriesItem> { public TestSeries() { Add(new TestSeriesItem()); Add(new TestSeriesItem()); Add(new TestSeriesItem()); } } public class TestSeriesItem { public TestSeriesItem() { Data = new TestData(); ValueMemberPath = "Value"; } public IEnumerable Data { get; set; } public string ValueMemberPath { get; set; } } public class TestData : ObservableCollection<TestDataItem> { private static Random rand = new Random(); public TestData() { for (int i = 0; i < 20; i++) { Add(new TestDataItem() { Label = i.ToString(), Value = rand.NextDouble() }); } } } public class TestDataItem { public string Label { get; set; } public double Value { get; set; } } public class SeriesDataContext : DependencyObject { public static DependencyProperty ItemProperty = DependencyProperty.Register("Item", typeof(object), typeof(SeriesDataContext), new PropertyMetadata(null, (o, e) => { })); public object Item { get { return GetValue(ItemProperty); } set { SetValue(ItemProperty, value); } } public static DependencyProperty OuterContextProperty = DependencyProperty.Register("OuterContext", typeof(object), typeof(SeriesDataContext), new PropertyMetadata(null, (o, e) => { })); public object OuterContext { get { return GetValue(OuterContextProperty); } set { SetValue(OuterContextProperty, value); } } public static DependencyProperty OwnerProperty = DependencyProperty.Register("Owner", typeof(XamDataChart), typeof(SeriesDataContext), new PropertyMetadata(null, (o, e) => { })); public XamDataChart Owner { get { return (XamDataChart)GetValue(OwnerProperty); } set { SetValue(OwnerProperty, value); } } } public class SeriesBindingInfo : FrameworkElement { public static DependencyProperty OwnerProperty = DependencyProperty.Register("Owner", typeof(XamDataChart), typeof(SeriesBindingInfo), new PropertyMetadata(null, (o, e) => { (o as SeriesBindingInfo).OnOwnerChanged(e); })); public XamDataChart Owner { get { return (XamDataChart)GetValue(OwnerProperty); } set { SetValue(OwnerProperty, value); } } public static DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(SeriesBindingInfo), new PropertyMetadata(null, (o, e) => { (o as SeriesBindingInfo).OnItemsSourceChanged(e); })); public IEnumerable ItemsSource { get { return (IEnumerable)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } public static DependencyProperty SeriesTemplateProperty = DependencyProperty.Register("SeriesTemplate", typeof(DataTemplate), typeof(SeriesBindingInfo), new PropertyMetadata(null, (o, e) => { (o as SeriesBindingInfo).OnSeriesTemplateChanged(e); } )); public DataTemplate SeriesTemplate { get { return (DataTemplate)GetValue(SeriesTemplateProperty); } set { SetValue(SeriesTemplateProperty, value); } } protected void OnItemsSourceChanged( DependencyPropertyChangedEventArgs e) { Clear(); if (e.OldValue != null) { UnRegCollection(e.OldValue as IEnumerable); } if (e.NewValue != null) { RegCollection(e.NewValue as IEnumerable); UpdateFromCollection(e.NewValue as IEnumerable); } } public static DependencyProperty OuterContextProperty = DependencyProperty.Register("OuterContext", typeof(object), typeof(SeriesBindingInfo), new PropertyMetadata(null, (o, e) => { })); public object OuterContext { get { return GetValue(OuterContextProperty); } set { SetValue(OuterContextProperty, value); } } protected void OnSeriesTemplateChanged( DependencyPropertyChangedEventArgs e) { Clear(); UpdateFromCollection(ItemsSource); } private void OnOwnerChanged( DependencyPropertyChangedEventArgs e) { SetBinding(DataContextProperty, new Binding("OuterContext") { Source = this }); Clear(); UpdateFromCollection(ItemsSource); } private void Clear() { if (Owner == null) { return; } Owner.Series.Clear(); } private void UpdateFromCollection( IEnumerable collection) { if (collection == null) { return; } foreach (object item in collection) { AddItem(item); } } private void AddItem( object item) { if (SeriesTemplate == null) { return; } Series series = SeriesTemplate.LoadContent() as Series; if (series != null) { SeriesDataContext context = new SeriesDataContext() { Item = item, Owner = Owner }; BindingOperations.SetBinding( context, SeriesDataContext.OuterContextProperty, new Binding("OuterContext") { Source = this }); series.DataContext = context; Axis xAxis = null; Axis yAxis = null; GetAxes(series, ref xAxis, ref yAxis); if (xAxis != null) { UpdateAxis(context, xAxis); } if (yAxis != null) { UpdateAxis(context, yAxis); } Owner.Series.Add(series); } } private void UpdateAxis(SeriesDataContext context, Axis axis) { axis.DataContext = context; if (!Owner.Axes.Contains(axis)) { Owner.Axes.Add(axis); } } private void GetAxes(Series series, ref Axis xAxis, ref Axis yAxis) { if (series is AnchoredCategorySeries) { xAxis = (series as AnchoredCategorySeries).XAxis; yAxis = (series as AnchoredCategorySeries).YAxis; } else if (series is RangeCategorySeries) { xAxis = (series as RangeCategorySeries).XAxis; yAxis = (series as RangeCategorySeries).YAxis; } else if (series is FinancialSeries) { xAxis = (series as FinancialSeries).XAxis; yAxis = (series as FinancialSeries).YAxis; } else if (series is ScatterBase) { xAxis = (series as ScatterBase).XAxis; yAxis = (series as ScatterBase).YAxis; } } private void RegCollection( IEnumerable collection) { if (collection is INotifyCollectionChanged) { (collection as INotifyCollectionChanged).CollectionChanged += SeriesBindingInfo_CollectionChanged; } } private void UnRegCollection( IEnumerable collection) { if (collection is INotifyCollectionChanged) { (collection as INotifyCollectionChanged).CollectionChanged -= SeriesBindingInfo_CollectionChanged; } } void SeriesBindingInfo_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e) { if (Owner == null) { return; } //this could be made more efficient of course. Clear(); UpdateFromCollection(ItemsSource); } } public class SeriesBinder : DependencyObject { public static DependencyProperty ContextBridgeProperty = DependencyProperty.RegisterAttached("ContextBridge", typeof(object), typeof(SeriesBinder), new PropertyMetadata(null, (o, e) => { SeriesBindingInfo sbi = GetBindingInfo(o); if (sbi != null) { sbi.OuterContext = e.NewValue; } })); public static DependencyProperty BindingInfoProperty = DependencyProperty.RegisterAttached("BindingInfo", typeof(SeriesBindingInfo), typeof(SeriesBinder), new PropertyMetadata(null, (o, e) => { if (o is XamDataChart) { SeriesBindingInfo prev = GetBindingInfo(o); if (prev != null && prev.Owner != null) { prev.Owner = null; } if (e.NewValue != null) { XamDataChart newOwner = o as XamDataChart; newOwner.SetBinding( SeriesBinder.ContextBridgeProperty, new Binding()); (e.NewValue as SeriesBindingInfo) .Owner = (XamDataChart)o; if (GetContextBridge(o) != null) { (e.NewValue as SeriesBindingInfo) .OuterContext = GetContextBridge(o); } } } })); public static void SetBindingInfo( DependencyObject target, SeriesBindingInfo info) { target.SetValue(BindingInfoProperty, info); } public static SeriesBindingInfo GetBindingInfo( DependencyObject target) { return (SeriesBindingInfo)target.GetValue(BindingInfoProperty); } public static void SetContextBridge( DependencyObject target, object context) { target.SetValue(ContextBridgeProperty, context); } public static object GetContextBridge( DependencyObject target) { return target.GetValue(ContextBridgeProperty); } }
The above example was assuming a situation where you wanted to share axes between all the series in the chartHowever, you could also use an approach like this to define seperate axes per item in your viewmodel collection, e.g:
<Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ig:XamDataChart Name="xamDataChart1" > <ig:XamDataChart.Axes> <ig:NumericYAxis x:Name="yAxis" /> </ig:XamDataChart.Axes> <local:SeriesBinder.BindingInfo> <local:SeriesBindingInfo ItemsSource="{Binding}"> <local:SeriesBindingInfo.SeriesTemplate> <DataTemplate> <ig:LineSeries ItemsSource="{Binding Item.Data}" ValueMemberPath="{Binding Item.ValueMemberPath}" YAxis="{Binding Path=Owner.Axes[0]}" > <ig:LineSeries.XAxis> <ig:CategoryXAxis ItemsSource="{Binding Item.Data}" Label="{}{Label}"/> </ig:LineSeries.XAxis> </ig:LineSeries> </DataTemplate> </local:SeriesBindingInfo.SeriesTemplate> </local:SeriesBindingInfo> </local:SeriesBinder.BindingInfo> </ig:XamDataChart> <Button Grid.Row="1" Content="Click" Click="Button_Click"/> </Grid>
Hope this helps! Let me know if you have questions.-Graham
Isn't there any simpler, straight forward way of achieving this? i.e. binidng a xamchart to a series collection from my view model? Series={Binding ..} should have been much better than defining dependency properties and custom templates.
The series collection is not allowed to be user replacable in the current version of the chart. Which is why this solution uses a binding proxy to add and remove the series. It would seem a lot simpler if it was not also allowing you to use data templates to specify how the series are configured. So if you don't need that logic, you can cut most of it out.
You could make a feature request for us to just make the Series property writeable, and we can see if we can smooth this use case out for you for the future.