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://es.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} ...
Something like this may work for tranforming the custom types to nominal types for the purpose of binding to the chart: https://es.infragistics.com/community/forums/f/ultimate-ui-for-wpf/48732/binding-to-collection-of-dynamic-objects/259244#259244
I'm prepping an SL5 environment so I can test some things.
My experimentation is indicating that ICustomTypeProvider works at the level of the bindings, and since the chart does lower level data access. for performance reasons. it evades this.You can bind the chart against a collection of such objects though, by generating types dynamically that have the required properties. The proxies can even be made to work with INotifyPropertyChanged.Please see the below sample:
<Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <igChart:XamDataChart x:Name="theChart"> <igChart:XamDataChart.Axes> <igChart:NumericYAxis x:Name="yAxis" /> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{Binding}" Label="{}{Label}"/> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> </igChart:XamDataChart.Series> </igChart:XamDataChart> <Button Grid.Row="1" Content="Click" Click="Button_Click" /> </Grid>
And code behind:
public partial class MainPage : UserControl { private ObservableCollection<Market> _data = null; public MainPage() { InitializeComponent(); _data = CreateTestData(); var view = new SolidifiedView(); view.Inner = _data; this.DataContext = view; var series = new StackedColumnSeries(); series.ItemsSource = view; series.XAxis = (CategoryXAxis)theChart.Axes[1]; series.YAxis = (NumericYAxis)theChart.Axes[0]; series.Series.Add(new StackedFragmentSeries() { Title = "Value1", ValueMemberPath = "Value1" }); series.Series.Add(new StackedFragmentSeries() { Title = "Value2", ValueMemberPath = "Value2" }); series.Series.Add(new StackedFragmentSeries() { Title = "Value3", ValueMemberPath = "Value3" }); theChart.Series.Add(series); } private ObservableCollection<Market> CreateTestData() { var ret = new ObservableCollection<Market>(); Market.AddProperty("Label", typeof(string)); Market.AddProperty("Value1", typeof(double)); Market.AddProperty("Value2", typeof(double)); Market.AddProperty("Value3", typeof(double)); Market m1 = new Market(); m1.SetPropertyValue("Label", "A"); m1.SetPropertyValue("Value1", 1d); m1.SetPropertyValue("Value2", 2d); m1.SetPropertyValue("Value3", 1d); ret.Add(m1); Market m2 = new Market(); m2.SetPropertyValue("Label", "B"); m2.SetPropertyValue("Value1", 2d); m2.SetPropertyValue("Value2", 1d); m2.SetPropertyValue("Value3", 2d); ret.Add(m2); Market m3 = new Market(); m3.SetPropertyValue("Label", "C"); m3.SetPropertyValue("Value1", 3d); m3.SetPropertyValue("Value2", 1d); m3.SetPropertyValue("Value3", 2d); ret.Add(m3); var p1 = m1.GetType().GetProperty("Value1"); return ret; } private void Button_Click(object sender, RoutedEventArgs e) { _data[0].SetPropertyValue("Value1", 2d); } } public class SolidifiedView : ObservableCollection<object> { private IList _inner; public IList Inner { get { return _inner; } set { IList oldValue = _inner; _inner = value; OnInnerChanged(oldValue, _inner); } } private void OnInnerChanged( IList oldValue, IList newValue) { if (oldValue != null && oldValue is INotifyCollectionChanged) { ((INotifyCollectionChanged)oldValue).CollectionChanged -= DataSolidifier_CollectionChanged; } if (newValue != null && newValue is INotifyCollectionChanged) { ((INotifyCollectionChanged)newValue).CollectionChanged += DataSolidifier_CollectionChanged; } ResetItems(); } private void ResetItems() { this.Clear(); if (Inner == null) { return; } foreach (var item in Inner) { var newItem = Proxy(item); Add(newItem); } } protected override void ClearItems() { foreach (var item in this) { if (item is ProxyBase) { ((ProxyBase)item) .Detach(); } } } void DataSolidifier_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (var item in e.NewItems) { var newItem = Proxy(item); this.Add(newItem); } break; case NotifyCollectionChangedAction.Remove: foreach (var item in e.OldItems) { var toRemove = this[e.OldStartingIndex]; if (toRemove is ProxyBase) { ((ProxyBase)toRemove).Detach(); } RemoveAt(e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Replace: foreach (var item in e.OldItems) { var toRemove = this[e.OldStartingIndex]; if (toRemove is ProxyBase) { ((ProxyBase)toRemove).Detach(); } RemoveAt(e.OldStartingIndex); } int index1 = e.NewStartingIndex; foreach (var item in e.NewItems) { var newItem = Proxy(item); this.Insert(index1++, newItem); } break; case NotifyCollectionChangedAction.Reset: ResetItems(); break; } } private object Proxy(object item) { if (item is ICustomTypeProvider) { var list = new PropertyInfoList( item as ICustomTypeProvider); var proxy = ProxyCreator.GetProxyInstance( list, item as ICustomTypeProvider); return proxy; } else { return item; } } } public class PropertyInfoItem { public string PropertyName { get; set; } public Type PropertyType { get; set; } public override string ToString() { return "{" + PropertyName + "}:[" + PropertyType.FullName + "]"; } } public class PropertyInfoList : List<PropertyInfoItem> { public PropertyInfoList(ICustomTypeProvider dyn) { foreach (var member in dyn.GetCustomType().GetProperties()) { Add(new PropertyInfoItem() { PropertyName = member.Name, PropertyType = member.PropertyType }); } } public override string ToString() { var sb = new StringBuilder(); var props = from item in this orderby item.PropertyName select item; foreach (var prop in props) { sb.Append(prop.ToString()); } return sb.ToString(); } } public abstract class ProxyBase : INotifyPropertyChanged { private ICustomTypeProvider _inner; public ProxyBase(ICustomTypeProvider inner) { _inner = inner; if (_inner is INotifyPropertyChanged) { ((INotifyPropertyChanged)_inner).PropertyChanged += ProxyBase_PropertyChanged; } } public void Detach() { if (_inner == null) { return; } if (_inner is INotifyPropertyChanged) { ((INotifyPropertyChanged)_inner).PropertyChanged -= ProxyBase_PropertyChanged; } _inner = null; } protected Dictionary<string, bool> _existingProperties = new Dictionary<string, bool>(); void ProxyBase_PropertyChanged( object sender, PropertyChangedEventArgs e) { RaisePropertyChanged(e.PropertyName); } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs( propertyName)); } } private Dictionary<string, CallSite<Func<CallSite, object, object>>> _sites = new Dictionary<string, CallSite<Func<CallSite, object, object>>>(); public object GetPropertyByName(string propertyName) { var info = _inner.GetCustomType().GetProperty(propertyName); return info.GetValue(_inner, null); } } public static class ProxyCreator { private static Dictionary< string, Func<ICustomTypeProvider, object>> _cachedTypes = new Dictionary< string, Func<ICustomTypeProvider, object>>(); private static object _lock = new object(); public static object GetProxyInstance( PropertyInfoList list, ICustomTypeProvider toWrap) { var type = GetProxyType(list); return type(toWrap); } private static Func<ICustomTypeProvider, object> GetProxyType(PropertyInfoList list) { string key = list.ToString(); Func<ICustomTypeProvider, object> output = null; lock (_lock) { if (_cachedTypes.TryGetValue(key, out output)) { return output; } } output = CreateProxyType(list); lock (_lock) { if (!_cachedTypes.ContainsKey(key)) { _cachedTypes.Add(key, output); } } return output; } private static AssemblyBuilder _ab = null; private static ModuleBuilder _mb = null; private static Func<ICustomTypeProvider, object> CreateProxyType(PropertyInfoList list) { lock (_lock) { if (_ab == null) { AssemblyName assmName = new AssemblyName("DynamicAssembly"); _ab = AppDomain.CurrentDomain.DefineDynamicAssembly( assmName, AssemblyBuilderAccess.Run); _mb = _ab.DefineDynamicModule(assmName.Name); _getPropertyValue = typeof(ProxyBase).GetMethod( "GetPropertyByName"); _baseCons = typeof(ProxyBase).GetConstructor( new Type[] { typeof(ICustomTypeProvider) }); } var tb = _mb.DefineType( Guid.NewGuid().ToString() + "__proxy", TypeAttributes.Public | TypeAttributes.Class, typeof(ProxyBase)); foreach (var prop in list) { CreateProperty(tb, prop); } ConstructorBuilder cons = tb.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(ICustomTypeProvider) } ); ILGenerator ilGen = cons.GetILGenerator(); ilGen.Emit(OpCodes.Ldarg_0); ilGen.Emit(OpCodes.Ldarg_1); ilGen.Emit(OpCodes.Call, _baseCons); ilGen.Emit(OpCodes.Ret); Type type = tb.CreateType(); ConstructorInfo typeCons = type.GetConstructor( new Type[] { typeof (ICustomTypeProvider) }); ParameterExpression param = System.Linq.Expressions.Expression.Parameter( typeof(ICustomTypeProvider)); Func<ICustomTypeProvider, object> res = System.Linq.Expressions.Expression.Lambda<Func<ICustomTypeProvider, object>>( System.Linq.Expressions.Expression.New(typeCons, param), param).Compile(); return res; } } private static MethodInfo _getPropertyValue; private static ConstructorInfo _baseCons; private static void CreateProperty(TypeBuilder tb, PropertyInfoItem prop) { PropertyBuilder pb = tb.DefineProperty( prop.PropertyName, PropertyAttributes.None, prop.PropertyType, null); MethodBuilder mb = tb.DefineMethod( "get_" + prop.PropertyName, MethodAttributes.Public | MethodAttributes.HideBySig, prop.PropertyType, null); ILGenerator ilGen = mb.GetILGenerator(); ilGen.Emit(OpCodes.Ldarg_0); ilGen.Emit(OpCodes.Ldstr, prop.PropertyName); ilGen.Emit(OpCodes.Call, _getPropertyValue); if (prop.PropertyType.IsValueType) { ilGen.Emit(OpCodes.Unbox, prop.PropertyType); ilGen.Emit(OpCodes.Ldobj, prop.PropertyType); } else { ilGen.Emit(OpCodes.Castclass, prop.PropertyType); } ilGen.Emit(OpCodes.Ret); pb.SetGetMethod(mb); } } public class Market : ICustomTypeProvider, INotifyPropertyChanged { // public String Date { get; set; } CustomTypeHelper<Market> _helper = null; public Market() { _helper = new CustomTypeHelper<Market>(); _helper.PropertyChanged += new PropertyChangedEventHandler(_helper_PropertyChanged); } void _helper_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(e.PropertyName)); } } public static void AddProperty(String name, Type propertyType) { CustomTypeHelper<Market>.AddProperty(name, propertyType); } public void SetPropertyValue(string propertyName, object value) { _helper.SetPropertyValue(propertyName, value); } public object GetPropertyValue(string propertyName) { return _helper.GetPropertyValue(propertyName); } public PropertyInfo[] GetProperties() { return _helper.GetProperties(); } public Type GetCustomType() { return _helper.GetCustomType(); } public event PropertyChangedEventHandler PropertyChanged; }
Note, I modified your Market type to raise PropertyChanged if the helper raises it.Hope this helps!-Graham
Dear Graham,
Thank u so much for ur help
i attached the main page for u, along with the 2 xmls am using
marketshareDS.xml is for reading the values and diplay them in the graph
CommonDS is for taking the no of distinct brands , according to this number, the stacked fragments are displayed.
am still in the process of binding the grid to the graph.
Hi,
Glad it helps!
Did you still have a question? I can't tell. In the sample you attached I had to change the line after InitializeComponent to:
_data = GenerateMarket() to point the sample back at your market data rather than the test data. And then I see columns.
BTW, you should bind the grid against the original data, not the Solidified View:
this.grdXmlData.ItemsSource = _data;
This is because the solidified view is read only proxies, so if you want to edit the source data from the grid, you should bind against the original source. The changes should be propagated through the proxies to the chart through INotifyPropertyChanged and INotifyCollectionChanged.
Hope this helps!
thx alot
Is there any article or blog that binds the pivot grid with the datachart??
am searching but there is only for webchart
I believe you can just set the Brush property on each fragment.
OK, i will now try to work with the pivotgrid and bind it :)
but i want to ask a question first,
can i chose the colors of the stacked fragments, because i don't want the randomely selected colors i get
i want to specify the color of the fragment when adding it, can i??
when I add that code, the grid is editable, and the changes are reflected in the chart.
It seems like this is a sufficient implementation of
GetIndexParameters:
return
new ParameterInfo[] { };