How do I bind series in XAML? Something like an ItemsSource (SeriesSource?) property with a SeriesTemplate - to set the DataMapping and other series properties - would make the most sense to me. If this is not possible, then does anyone have an attached property that would do the equivalent work.
The Series property on XamChart is read only, so although you can add items to it, you wouldn't be able to bind a collection directly to it. You are correct in your surmise, however, that there are all number of fancy things you can do with attached properties to get the effect you are after. I've had a go and maybe this will get you started:
The Xaml:
<Window x:Class="WpfApplication22.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
x:Name="theWindow"
xmlns:igChart="http://infragistics.com/Chart"
xmlns:local="clr-namespace:WpfApplication22">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<igChart:XamChart x:Name="theChart">
<local:SeriesBinder.BindingInfo>
<local:SeriesBindingInfo
ItemsSource="{Binding}">
<local:SeriesBindingInfo.SeriesTemplate>
<igChart:Series
ChartType="{Binding ChartType}"
DataMapping="{Binding DataMapping}"
DataSource="{Binding DataSource}">
</igChart:Series>
</local:SeriesBindingInfo.SeriesTemplate>
</local:SeriesBindingInfo>
</local:SeriesBinder.BindingInfo>
</igChart:XamChart>
<Button Grid.Row="1"
Click="Button_Click">
Add Series
</Button>
</Grid>
</Window>
The window's code behind:
public partial class Window1 : Window
{
public Window1()
InitializeComponent();
DataContext = GetSampleData();
}
private ObservableCollection<TestSeriesItem> GetSampleData()
ObservableCollection<TestSeriesItem> data = new ObservableCollection<TestSeriesItem>()
new TestSeriesItem()
ChartType = ChartType.Column,
DataMapping = "Label = Label; Value = Value",
DataSource = new ObservableCollection<TestDataItem>()
new TestDataItem()
Label = "1",
Value = 1
},
Label = "2",
Value = 2
ChartType = ChartType.Line,
};
return data;
private void Button_Click(object sender, RoutedEventArgs e)
((ObservableCollection<TestSeriesItem>)DataContext).Add(
ChartType = ChartType.Area,
Value = 5
Value = 6
});
public class TestSeriesItem
public ChartType ChartType { get; set; }
public string DataMapping { get; set; }
public object DataSource { get; set; }
public class TestDataItem
public string Label { get; set; }
public double Value { get; set; }
And now, the attached property and helper classes:
class DependencyPropertyEnumerator
public static IEnumerable<DependencyProperty> DependencyProperties(Type type)
var props = from prop in type.GetProperties(BindingFlags.Instance |
BindingFlags.Public | BindingFlags.FlattenHierarchy)
join field in type.GetFields(BindingFlags.Static |
on prop.Name + "Property" equals field.Name into propGroup
from item in propGroup.DefaultIfEmpty(null)
where item != null
select (DependencyProperty)item.GetValue(null);
return props;
public static bool DependencyPropertyHasBinding(
FrameworkContentElement obj,
DependencyProperty dp,
out BindingExpression be)
be = null;
if (obj == null)
return false;
be = obj.GetBindingExpression
(dp);
if (be != null)
return true;
public static bool DependencyPropertyHasValue(
DependencyProperty dp, out object value)
value = null;
object localValue = obj.ReadLocalValue(dp);
object defaultValue =
dp.GetMetadata(typeof(Series)).DefaultValue;
if (localValue != DependencyProperty.UnsetValue)
if ((localValue == null && defaultValue != null)
|| !localValue.Equals(defaultValue))
value = localValue;
class SeriesBindingInfo
: FrameworkElement
private XamChart targetChart;
public XamChart TargetChart
get
return targetChart;
set
targetChart = value;
//bridge the data context from the parent hierarchy.
Binding binding = new Binding("DataContext")
Source = targetChart
SetBinding(DataContextProperty, binding);
Clear();
UpdateFromCollection(ItemsSource);
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource",
typeof(IEnumerable), typeof(SeriesBindingInfo),
new FrameworkPropertyMetadata(null,
(o, e) =>
(o as SeriesBindingInfo).OnItemsSourceChanged(e);
}));
public IEnumerable ItemsSource
return (IEnumerable)GetValue(ItemsSourceProperty);
SetValue(ItemsSourceProperty, value);
public static DependencyProperty SeriesTemplateProperty =
DependencyProperty.Register("SeriesTemplate",
typeof(Series), typeof(SeriesBindingInfo),
(o as SeriesBindingInfo).OnSeriesTemplateChanged(e);
));
public Series SeriesTemplate
return (Series)GetValue(SeriesTemplateProperty);
SetValue(SeriesTemplateProperty, value);
protected void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
if (e.OldValue != null)
UnRegCollection(e.OldValue as IEnumerable);
if (e.NewValue != null)
RegCollection(e.NewValue as IEnumerable);
UpdateFromCollection(e.NewValue as IEnumerable);
protected void OnSeriesTemplateChanged(DependencyPropertyChangedEventArgs e)
private void Clear()
if (TargetChart == null)
return;
TargetChart.Series.Clear();
private void UpdateFromCollection(IEnumerable collection)
if (collection == null)
foreach (object item in collection)
AddItem(item);
private void AddItem(object item)
if (SeriesTemplate == null)
Series series = new Series();
series.DataContext = item;
SetSeriesBindings(series);
TargetChart.Series.Add(series);
private void SetSeriesBindings(Series series)
foreach (DependencyProperty dp in
DependencyPropertyEnumerator.DependencyProperties(typeof(Series)))
object value;
BindingExpression be;
if (DependencyPropertyEnumerator.
DependencyPropertyHasBinding(
SeriesTemplate, dp, out be))
series.SetBinding(dp, be.ParentBinding);
else if (DependencyPropertyEnumerator.
DependencyPropertyHasValue(
SeriesTemplate, dp, out value))
series.SetValue(dp, value);
private void RegCollection(IEnumerable collection)
if (collection is INotifyCollectionChanged)
(collection as INotifyCollectionChanged).CollectionChanged
+= new NotifyCollectionChangedEventHandler(
SeriesBindingInfo_CollectionChanged);
private void UnRegCollection(IEnumerable collection)
-= new NotifyCollectionChangedEventHandler(
void SeriesBindingInfo_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
//this could be made more efficient of course.
class SeriesBinder
: DependencyObject
public static DependencyProperty BindingInfoProperty =
DependencyProperty.RegisterAttached("BindingInfo",
typeof(SeriesBindingInfo), typeof(SeriesBinder),
new PropertyMetadata(null,
if (o is XamChart)
SeriesBindingInfo prev = GetBindingInfo(o);
if (prev != null && prev.TargetChart != null)
prev.TargetChart = null;
(e.NewValue as SeriesBindingInfo).TargetChart =
(XamChart)o;
public static void SetBindingInfo(DependencyObject target, SeriesBindingInfo info)
target.SetValue(BindingInfoProperty, info);
public static SeriesBindingInfo GetBindingInfo(DependencyObject target)
return (SeriesBindingInfo)target.GetValue(BindingInfoProperty);
Basically what's going on here is that the SeriesBindingInfo that you attach to the chart will listen to changes in its ItemsSource and create series objects based on a template series you provide, making sure that the data context is set to the respective item in the ItemsSource. It creates a series based on the template by basically copying any dependency property values and bindings into a new Series object. It doesn't do anything for CLR properties currently.
Is this the sort of thing you were thinking of?
-Graham
This is phenomenal!!! Seriously. Great work, Graham.
I wasn't sure I'd get a response, so I began working on my own implementation. Interestingly, mine is called "ChartBinder" with a Dependency property (Attached) named "ChartSeriesSourceProperty" of type "ChartSeriesCollection." The ChartSeriesCollection is an ObservableCollection and has a Chart:XamChart property. I watch the collection and add/remove chart series as necessary.
Your method is much more generic and, well, elegant. I particularly like the templating part. I'm going to give it a try.
I was looking at other 3rd party controls for this same functionality. Only one other major vendor seems to do it, but it is a very awkward implementation.
Thanks!
Glad you like it!
Yeah, there are probably other ways to achieve this too, I was experimenting a little bit with DataTemplates, but the Series isn't a visual control, so you would have to house it in a visual control to do the templating with a DataTemplate, but that was starting to seem less elegant.
So, this is the most elegant approach I've come up with so far. There may be an even cleaner way to do this, and if it comes to me, I'll try to remember to come back and update this.
Here's a reposting which will hopefully be less mangled.
<Window x:Class="WpfApplication22.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300" x:Name="theWindow" xmlns:igChart="http://infragistics.com/Chart" xmlns:local="clr-namespace:WpfApplication22"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="1*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <igChart:XamChart x:Name="theChart"> <local:SeriesBinder.BindingInfo> <local:SeriesBindingInfo ItemsSource="{Binding}"> <local:SeriesBindingInfo.SeriesTemplate> <igChart:Series ChartType="{Binding ChartType}" DataMapping="{Binding DataMapping}" DataSource="{Binding DataSource}"> </igChart:Series> </local:SeriesBindingInfo.SeriesTemplate> </local:SeriesBindingInfo> </local:SeriesBinder.BindingInfo> </igChart:XamChart> <Button Grid.Row="1" Click="Button_Click"> Add Series </Button> </Grid> </Window>
And the window code behind:
public partial class Window1 : Window { public Window1() { InitializeComponent(); DataContext = GetSampleData(); } private ObservableCollection<TestSeriesItem> GetSampleData() { ObservableCollection<TestSeriesItem> data = new ObservableCollection<TestSeriesItem>() { new TestSeriesItem() { ChartType = ChartType.Column, DataMapping = "Label = Label; Value = Value", DataSource = new ObservableCollection<TestDataItem>() { new TestDataItem() { Label = "1", Value = 1 }, new TestDataItem() { Label = "2", Value = 2 } } }, new TestSeriesItem() { ChartType = ChartType.Line, DataMapping = "Label = Label; Value = Value", DataSource = new ObservableCollection<TestDataItem>() { new TestDataItem() { Label = "1", Value = 1 }, new TestDataItem() { Label = "2", Value = 2 } } } }; return data; } private void Button_Click(object sender, RoutedEventArgs e) { ((ObservableCollection<TestSeriesItem>)DataContext).Add( new TestSeriesItem() { ChartType = ChartType.Area, DataMapping = "Label = Label; Value = Value", DataSource = new ObservableCollection<TestDataItem>() { new TestDataItem() { Label = "1", Value = 5 }, new TestDataItem() { Label = "2", Value = 6 } } }); } } public class TestSeriesItem { public ChartType ChartType { get; set; } public string DataMapping { get; set; } public object DataSource { get; set; } } public class TestDataItem { public string Label { get; set; } public double Value { get; set; } }
And the attached properties and helper classes
class DependencyPropertyEnumerator { public static IEnumerable<DependencyProperty> DependencyProperties(Type type) { var props = from prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy) join field in type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy) on prop.Name + "Property" equals field.Name into propGroup from item in propGroup.DefaultIfEmpty(null) where item != null select (DependencyProperty)item.GetValue(null); return props; } public static bool DependencyPropertyHasBinding( FrameworkContentElement obj, DependencyProperty dp, out BindingExpression be) { be = null; if (obj == null) { return false; } be = obj.GetBindingExpression (dp); if (be != null) { return true; } return false; } public static bool DependencyPropertyHasValue( FrameworkContentElement obj, DependencyProperty dp, out object value) { value = null; if (obj == null) { return false; } object localValue = obj.ReadLocalValue(dp); object defaultValue = dp.GetMetadata(typeof(Series)).DefaultValue; if (localValue != DependencyProperty.UnsetValue) { if ((localValue == null && defaultValue != null) || !localValue.Equals(defaultValue)) { value = localValue; return true; } } return false; } } class SeriesBindingInfo : FrameworkElement { private XamChart targetChart; public XamChart TargetChart { get { return targetChart; } set { targetChart = value; //bridge the data context from the parent hierarchy. Binding binding = new Binding("DataContext") { Source = targetChart }; SetBinding(DataContextProperty, binding); Clear(); UpdateFromCollection(ItemsSource); } } public static DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(SeriesBindingInfo), new FrameworkPropertyMetadata(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(Series), typeof(SeriesBindingInfo), new FrameworkPropertyMetadata(null, (o, e) => { (o as SeriesBindingInfo).OnSeriesTemplateChanged(e); } )); public Series SeriesTemplate { get { return (Series)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); } } protected void OnSeriesTemplateChanged(DependencyPropertyChangedEventArgs e) { Clear(); UpdateFromCollection(ItemsSource); } private void Clear() { if (TargetChart == null) { return; } TargetChart.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 = new Series(); series.DataContext = item; SetSeriesBindings(series); TargetChart.Series.Add(series); } private void SetSeriesBindings(Series series) { if (SeriesTemplate == null) { return; } foreach (DependencyProperty dp in DependencyPropertyEnumerator.DependencyProperties(typeof(Series))) { object value; BindingExpression be; if (DependencyPropertyEnumerator. DependencyPropertyHasBinding( SeriesTemplate, dp, out be)) { series.SetBinding(dp, be.ParentBinding); } else if (DependencyPropertyEnumerator. DependencyPropertyHasValue( SeriesTemplate, dp, out value)) { series.SetValue(dp, value); } } } private void RegCollection(IEnumerable collection) { if (collection is INotifyCollectionChanged) { (collection as INotifyCollectionChanged).CollectionChanged += new NotifyCollectionChangedEventHandler( SeriesBindingInfo_CollectionChanged); } } private void UnRegCollection(IEnumerable collection) { if (collection is INotifyCollectionChanged) { (collection as INotifyCollectionChanged).CollectionChanged -= new NotifyCollectionChangedEventHandler( SeriesBindingInfo_CollectionChanged); } } void SeriesBindingInfo_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (TargetChart == null) { return; } //this could be made more efficient of course. Clear(); UpdateFromCollection(ItemsSource); } } class SeriesBinder : DependencyObject { public static DependencyProperty BindingInfoProperty = DependencyProperty.RegisterAttached("BindingInfo", typeof(SeriesBindingInfo), typeof(SeriesBinder), new PropertyMetadata(null, (o, e) => { if (o is XamChart) { SeriesBindingInfo prev = GetBindingInfo(o); if (prev != null && prev.TargetChart != null) { prev.TargetChart = null; } if (e.NewValue != null) { (e.NewValue as SeriesBindingInfo).TargetChart = (XamChart)o; } } })); public static void SetBindingInfo(DependencyObject target, SeriesBindingInfo info) { target.SetValue(BindingInfoProperty, info); } public static SeriesBindingInfo GetBindingInfo(DependencyObject target) { return (SeriesBindingInfo)target.GetValue(BindingInfoProperty); } }
I am having one issue that I cannot figure out when using the SeriesBindingInfo classes. If I reload the data that is used with the chart, my chart does not refresh automatically. Is there something that I am missing in order to get this to work properly?
Could you share the code you are using to refresh the data?
Could you post the solution in a project file inside a .zip?
Thank you
The idea of this code is to enable MVVM scenarios.
Hi,
Is this code applicable for implementation through MVVM also?
Thanks,
VJ
I solved my problem !
Your code doesn't manage Multibinding... I just modified your SetSeriesBindings method like that :
if (!typeof(MultiBindingExpression).IsAssignableFrom(value.GetType()))
else
series.SetBinding(dp, ((MultiBindingExpression)value).ParentMultiBinding);
It's pretty dirty but it works.
Steph
Hello Graham,
I have a little problem with your SeriesBinder code (the WPF version with xamchart) ; if I use multibinding or triggers, I have an exception which says that the object is not sharable ; see my code below :
<core:SeriesBinder.BindingInfo>
<core:SeriesBindingInfo ItemsSource="{Binding SeriesCollection}">
<core:SeriesBindingInfo.SeriesTemplate>
<igCA:Series
DataSource="{Binding DataSource}"
StrokeThickness="2"
Label="{Binding Label}"
>
<igCA:Series.Fill>
<MultiBinding Converter="{StaticResource myConv}">
<MultiBinding.Bindings>
<Binding Path="ParentObject.Consolidation.Name"/>
<Binding Path="ParentObject.Consolidation.Colour"/>
</MultiBinding.Bindings>
</MultiBinding>
</igCA:Series.Fill>
</igCA:Series>
</core:SeriesBindingInfo.SeriesTemplate>
</core:SeriesBindingInfo>
</core:SeriesBinder.BindingInfo>
The thing is, with a xamDataChart, it works very well cause I think the whole code is inside a DataTemplate.... is there a way to have the same thing with a xamChart ?
Thanks