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!
forum mangled that post a bit, let me know if you need it in project form.