I am using xamdataChart with multiple datachart, scatterline/scattercontour etc. I would like to dynamically add series to the chart via MVVM.
I saw a solution for this but it is quite old, from 8 years ago. I modified it a little to accept ScatterContours but I am having a problem onValueMemberPath.
I nee to have 3 datachart,two off them add scatter line, and the last one add scatter contour.
Can someone notify the problem, or simple provide my a more recent solution to the problem?
Thank you!
ps: I couln'tupload my zip so hre is my code
Main window.xaml
<Window xmlns:ig="http://schemas.infragistics.com/xaml" x:Class="SeriesBinder.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SeriesBinder" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="50" /> </Grid.RowDefinitions> <ig:XamDataChart Margin="2" > <local:SeriesBinderBehavior.SeriesBinder> <local:SeriesBinderInfo SeriesSource="{Binding SeriesSource}" ItemsSourcePath="Source" XMemberPath="XPath" YMemberPath="YPath" TypePath="Type"/> </local:SeriesBinderBehavior.SeriesBinder> </ig:XamDataChart> <ig:XamDataChart Grid.Row="1" > <local:SeriesBinderBehavior.SeriesBinder> <local:SeriesBinderInfo SeriesSource="{Binding SeriesSource2}" ItemsSourcePath="Source" XMemberPath="XPath" YMemberPath="YPath" TypePath="Type"/> <!-- ValuMemberPathmissing --> </local:SeriesBinderBehavior.SeriesBinder> </ig:XamDataChart> <Button Content="Add Series" Command="{Binding AddSeries}" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="12,15,0,0" Name="button1" VerticalAlignment="Top" Width="75" /> <Button Content="Remove Series" Command="{Binding RemoveSeries}" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="93,15,0,0" Name="button2" VerticalAlignment="Top" Width="94" /> <Button Content="Clear Series" Command="{Binding ClearSeries}" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="193,15,0,0" Name="button3" VerticalAlignment="Top" Width="75" /> <Button Content="Reset Source" Command="{Binding ResetSeries}" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="274,15,0,0" Name="button4" VerticalAlignment="Top" Width="75" /> </Grid> </Window>
Main window.cs
using Infragistics.Controls.Charts; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Windows; using System.Windows.Data; using System.Windows.Input; namespace SeriesBinder { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new MainViewModel(); } } #region Series Binder public class SeriesBinderBehavior : DependencyObject { public static readonly DependencyProperty SeriesBinderProperty = DependencyProperty.RegisterAttached( "SeriesBinder", typeof(SeriesBinderInfo), typeof(SeriesBinderBehavior), new PropertyMetadata(null, (o, e) => { OnSeriesBiderChaged(o as XamDataChart, e.OldValue as SeriesBinderInfo, e.NewValue as SeriesBinderInfo); })); public static SeriesBinderInfo GetSeriesBinder(DependencyObject target) { return target.GetValue(SeriesBinderProperty) as SeriesBinderInfo; } public static void SetSeriesBinder(DependencyObject target, object value) { target.SetValue(SeriesBinderProperty, value); } private static void OnSeriesBiderChaged( XamDataChart chart, SeriesBinderInfo oldVal, SeriesBinderInfo newVal) { if (chart == null) { return; } if (oldVal != null) { oldVal.DetachOwner(chart); } if (newVal != null) { newVal.AttachOwner(chart); } } } public class SeriesBinderInfo : FrameworkElement { #region Fields private XamDataChart _owner; Dictionary<object, Series> _seriesObjectMapper; #endregion #region Constructor public SeriesBinderInfo() { _seriesObjectMapper = new Dictionary<object, Series>(); } #endregion #region Initializing Methods public void AttachOwner(XamDataChart chart) { if (_owner != null) { DetachOwner(_owner); } _owner = chart; SetBinding(DataContextProperty, new Binding("DataContext") { Source = _owner }); } public void DetachOwner(XamDataChart _owner) { _owner = null; _seriesObjectMapper.Clear(); ClearValue(DataContextProperty); } #endregion #region Properties #region SeriesSource private const string SeriesSourcePropertyName = "SeriesSource"; public IEnumerable SeriesSource { get { return (IEnumerable)GetValue(SeriesSourceProperty); } set { SetValue(SeriesSourceProperty, value); } } public static readonly DependencyProperty SeriesSourceProperty = DependencyProperty.Register( SeriesSourcePropertyName, typeof(IEnumerable), typeof(SeriesBinderInfo), new PropertyMetadata(null)); #endregion #region ItemsSourcePath private const string ItemsSourcePathPropertyName = "ItemsSourcePath"; public string ItemsSourcePath { get { return (string)GetValue(ItemsSourcePathProperty); } set { SetValue(ItemsSourcePathProperty, value); } } public static readonly DependencyProperty ItemsSourcePathProperty = DependencyProperty.Register( ItemsSourcePathPropertyName, typeof(string), typeof(SeriesBinderInfo), new PropertyMetadata(null)); #endregion #region XMemberPath private const string XMemberPathPropertyName = "XMemberPath"; public string XMemberPath { get { return (string)GetValue(XMemberPathProperty); } set { SetValue(XMemberPathProperty, value); } } public static readonly DependencyProperty XMemberPathProperty = DependencyProperty.Register( XMemberPathPropertyName, typeof(string), typeof(SeriesBinderInfo), new PropertyMetadata(null)); #endregion #region YMemberPath private const string YMemberPathPropertyName = "YMemberPath"; public string YMemberPath { get { return (string)GetValue(YMemberPathProperty); } set { SetValue(YMemberPathProperty, value); } } public static readonly DependencyProperty YMemberPathProperty = DependencyProperty.Register( YMemberPathPropertyName, typeof(string), typeof(SeriesBinderInfo), new PropertyMetadata(null)); #endregion #region valueMemberPath private const string ValueMemberPathPropertyName = "ValueMemberPath"; public string ValueMemberPath { get { return (string)GetValue(ValueMemberPathProperty); } set { SetValue(ValueMemberPathProperty, value); } } public static readonly DependencyProperty ValueMemberPathProperty = DependencyProperty.Register( ValueMemberPathPropertyName, typeof(string), typeof(SeriesBinderInfo), new PropertyMetadata(null)); #endregion #region TypePath private const string TypePathPropertyName = "TypePath"; public string TypePath { get { return (string)GetValue(TypePathProperty); } set { SetValue(TypePathProperty, value); } } public static readonly DependencyProperty TypePathProperty = DependencyProperty.Register( TypePathPropertyName, typeof(string), typeof(SeriesBinderInfo), new PropertyMetadata(null)); #endregion #endregion #region Event Handlers void SeriesBinderInfo_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: { object seriesSource = e.NewItems[0]; AddSeries(seriesSource); } break; case NotifyCollectionChangedAction.Remove: { object seriesSource = e.OldItems[0]; RemoveSeries(seriesSource); } break; case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Reset: { GenerateSeries(); } break; default: break; } } #endregion #region Helper Methods private void RemoveSeries(object seriesSource) { Series s = _seriesObjectMapper[seriesSource]; SeriesType type = (SeriesType)seriesSource.GetType().GetProperty(TypePath).GetValue(seriesSource, null); UpdateSeriesProperties(s, type, seriesSource, true, true); _owner.Series.Remove(s); _seriesObjectMapper.Remove(seriesSource); } private void AddSeries(object seriesSource) { SeriesType type = (SeriesType)seriesSource.GetType().GetProperty(TypePath).GetValue(seriesSource, null); Series series = CreateSeries(type, seriesSource); _owner.Series.Add(series); _seriesObjectMapper.Add(seriesSource, series); } private void GenerateSeries() { _owner.Series.Clear(); _owner.Axes.Clear(); foreach (var item in SeriesSource) { AddSeries(item); } } private Series CreateSeries(SeriesType type, object seriesSource) { Series series = null; switch (type) { case SeriesType.LineSeries: { series = new LineSeries(); UpdateSeriesProperties(series, type, seriesSource, true, false); } break; case SeriesType.ColumnSeries: { series = new ColumnSeries(); UpdateSeriesProperties(series, type, seriesSource, true, false); } break; case SeriesType.ScatterSeries: { series = new ScatterSeries(); UpdateSeriesProperties(series, type, seriesSource, true, false); } break; case SeriesType.ScatterLineSeries: { series = new ScatterLineSeries(); UpdateSeriesProperties(series, type, seriesSource, true, false); } break; case SeriesType.ScatterContourSeries: { series = new ScatterContourSeries(); UpdateSeriesProperties(series, type, seriesSource, true, false); } break; default: break; } return series; } private void UpdateSeriesProperties(Series series, SeriesType type, object seriesSource, bool shouldRenewAxes, bool clearOnly) { if (series != null) { switch (type) { case SeriesType.LineSeries: case SeriesType.ColumnSeries: { HorizontalAnchoredCategorySeries category = series as HorizontalAnchoredCategorySeries; category.ClearValue(HorizontalAnchoredCategorySeries.ValueMemberPathProperty); category.ClearValue(HorizontalAnchoredCategorySeries.ItemsSourceProperty); if (!clearOnly) { category.SetBinding(HorizontalAnchoredCategorySeries.ValueMemberPathProperty, new Binding(YMemberPath) { Source = seriesSource }); category.SetBinding(HorizontalAnchoredCategorySeries.ItemsSourceProperty, new Binding(ItemsSourcePath) { Source = seriesSource }); } if (shouldRenewAxes) { if (category.XAxis != null) _owner.Axes.Remove(category.XAxis); if (category.YAxis != null) _owner.Axes.Remove(category.YAxis); if (!clearOnly) { CategoryXAxis xAxis = new CategoryXAxis(); xAxis.SetBinding(CategoryXAxis.ItemsSourceProperty, new Binding(ItemsSourcePath) { Source = seriesSource }); xAxis.Label = "{" + seriesSource.GetType().GetProperty(XMemberPath).GetValue(seriesSource, null) + "}"; NumericYAxis yAxis = new NumericYAxis(); _owner.Axes.Add(xAxis); _owner.Axes.Add(yAxis); category.XAxis = xAxis; category.YAxis = yAxis; } } } break; case SeriesType.ScatterSeries: case SeriesType.ScatterLineSeries: { ScatterBase scatter = series as ScatterBase; scatter.ClearValue(ScatterBase.XMemberPathProperty); scatter.ClearValue(ScatterBase.YMemberPathProperty); scatter.ClearValue(ScatterBase.ItemsSourceProperty); if (!clearOnly) { scatter.SetBinding(ScatterBase.XMemberPathProperty, new Binding(XMemberPath) { Source = seriesSource }); scatter.SetBinding(ScatterBase.YMemberPathProperty, new Binding(YMemberPath) { Source = seriesSource }); scatter.SetBinding(ScatterBase.ItemsSourceProperty, new Binding(ItemsSourcePath) { Source = seriesSource }); } if (shouldRenewAxes) { if (scatter.XAxis != null) _owner.Axes.Remove(scatter.XAxis); if (scatter.YAxis != null) _owner.Axes.Remove(scatter.YAxis); if (!clearOnly) { NumericXAxis xAxis = new NumericXAxis(); NumericYAxis yAxis = new NumericYAxis(); _owner.Axes.Add(xAxis); _owner.Axes.Add(yAxis); scatter.XAxis = xAxis; scatter.YAxis = yAxis; } } } break; case SeriesType.ScatterContourSeries: { ScatterContourSeries scatterContour = series as ScatterContourSeries; scatterContour.ClearValue(ScatterTriangulationSeries.XMemberPathProperty); scatterContour.ClearValue(ScatterTriangulationSeries.YMemberPathProperty); scatterContour.ClearValue(ScatterContourSeries.ValueMemberPathProperty); scatterContour.ClearValue(ScatterTriangulationSeries.ItemsSourceProperty); if (!clearOnly) { scatterContour.SetBinding(ScatterTriangulationSeries.XMemberPathProperty, new Binding(XMemberPath) { Source = seriesSource }); scatterContour.SetBinding(ScatterTriangulationSeries.YMemberPathProperty, new Binding(YMemberPath) { Source = seriesSource }); scatterContour.SetBinding(ScatterContourSeries.ValueMemberPathProperty, new Binding(ValueMemberPath) { Source = seriesSource }); scatterContour.SetBinding(ScatterTriangulationSeries.ItemsSourceProperty, new Binding(ItemsSourcePath) { Source = seriesSource }); } if (shouldRenewAxes) { if (scatterContour.XAxis != null) _owner.Axes.Remove(scatterContour.XAxis); if (scatterContour.YAxis != null) _owner.Axes.Remove(scatterContour.YAxis); if (!clearOnly) { NumericXAxis xAxis = new NumericXAxis(); NumericYAxis yAxis = new NumericYAxis(); _owner.Axes.Add(xAxis); _owner.Axes.Add(yAxis); scatterContour.XAxis = xAxis; scatterContour.YAxis = yAxis; // scatterContour.ValueMemberPath = } } ; } break; default: break; } } } #endregion #region Base Overrides protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { switch (e.Property.Name) { case SeriesSourcePropertyName: { if (e.OldValue != null && e.OldValue is INotifyCollectionChanged) (e.OldValue as INotifyCollectionChanged).CollectionChanged -= SeriesBinderInfo_CollectionChanged; if (e.NewValue != null && e.NewValue is INotifyCollectionChanged) (e.NewValue as INotifyCollectionChanged).CollectionChanged += SeriesBinderInfo_CollectionChanged; GenerateSeries(); } break; case XMemberPathPropertyName: case YMemberPathPropertyName: case ValueMemberPathPropertyName: case TypePathPropertyName: { if (SeriesSource != null) GenerateSeries(); } break; default: break; } base.OnPropertyChanged(e); } #endregion } #endregion #region View Model public class MainViewModel : PropertyChangedNotifier { private ObservableCollection<SeriesViewModel> seriesSource; private ObservableCollection<SeriesViewModel> seriesSource2; public MainViewModel() { SeriesSource = new ObservableCollection<SeriesViewModel> { new SeriesViewModel { Source = ChartData.GenerateData(10), Type = SeriesType.ColumnSeries, XPath = "Date", YPath = "Value", }, new SeriesViewModel { Source = ChartData.GenerateData(22), Type = SeriesType.LineSeries, XPath = "Label", YPath = "Value", }, new SeriesViewModel { Source = ChartData.GenerateData(20), Type = SeriesType.ScatterSeries, XPath = "Index", YPath = "Value", }, new SeriesViewModel { Source = ChartData.GenerateData(15), Type = SeriesType.ScatterLineSeries, XPath = "Index", YPath = "Value", }, }; SeriesSource2 = new ObservableCollection<SeriesViewModel>(); } public ObservableCollection<SeriesViewModel> SeriesSource { get { return seriesSource; } set { seriesSource = value; OnPropertyChagned("SeriesSource"); } } public ObservableCollection<SeriesViewModel> SeriesSource2 { get { return seriesSource2; } set { seriesSource2 = value; OnPropertyChagned("SeriesSource2"); } } public ICommand AddSeries { get { return new RelayCommand<object>( new Action<object>( (obj) => { SeriesSource2.Add( new SeriesViewModel { Source = ChartData.GenerateData(10), Type = SeriesType.ScatterContourSeries, XPath = "Index", YPath = "Index", ValueMemberPath = "Value" }); SeriesSource.Add( new SeriesViewModel { Source = ChartData.GenerateData(10), Type = SeriesType.ScatterLineSeries, XPath = "Index", YPath = "Value", ValueMemberPath = "Value" }); })); } } public ICommand RemoveSeries { get { return new RelayCommand<object>( new Action<object>( (obj) => { if (SeriesSource.Count > 0) SeriesSource.RemoveAt(0); })); } } public ICommand ClearSeries { get { return new RelayCommand<object>( new Action<object>( (obj) => { SeriesSource.Clear(); })); } } public ICommand ResetSeries { get { return new RelayCommand<object>( new Action<object>( (obj) => { SeriesSource = new ObservableCollection<SeriesViewModel> { new SeriesViewModel { Source = ChartData.GenerateData(10), Type = SeriesType.ColumnSeries, XPath = "Date", YPath = "Value", }, new SeriesViewModel { Source = ChartData.GenerateData(22), Type = SeriesType.LineSeries, XPath = "Label", YPath = "Value", }, new SeriesViewModel { Source = ChartData.GenerateData(20), Type = SeriesType.ScatterSeries, XPath = "Index", YPath = "Value", }, new SeriesViewModel { Source = ChartData.GenerateData(15), Type = SeriesType.ScatterLineSeries, XPath = "Index", YPath = "Value", }, }; })); } } } public class SeriesViewModel : PropertyChangedNotifier { private SeriesType type; public SeriesType Type { get { return type; } set { type = value; OnPropertyChagned("Type"); } } private IEnumerable source; public IEnumerable Source { get { return source; } set { source = value; OnPropertyChagned("Source"); } } private string xPath; public string XPath { get { return xPath; } set { xPath = value; OnPropertyChagned("XPath"); } } private string yPath; public string YPath { get { return yPath; } set { yPath = value; OnPropertyChagned("XPath"); } } private string valueMemberPath; public string ValueMemberPath { get { return valueMemberPath; } set { valueMemberPath = value; OnPropertyChagned("ValueMemberPath"); } } } public class ChartData { public DateTime Date { get; set; } public int Index { get; set; } public string Label { get; set; } public double Value { get; set; } public static ObservableCollection<ChartData> GenerateData(int count) { ObservableCollection<ChartData> data = new ObservableCollection<ChartData>(); Random r = new Random(); ChartData data0 = new ChartData { Date = DateTime.Now, Index = 0, Value = r.Next(20, 40), Label = "Item 0" }; data.Add(data0); for (int i = 1; i < count; i++) { ChartData dat = new ChartData { Date = DateTime.Now.AddDays(i), Index = i, Label = "Item " + i, }; if (r.NextDouble() > .5) dat.Value = data[i - 1].Value + r.NextDouble(); else dat.Value = data[i - 1].Value - r.NextDouble(); data.Add(dat); } return data; } } public class PropertyChangedNotifier : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChagned(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } } public enum SeriesType { LineSeries, ColumnSeries, ScatterSeries, ScatterLineSeries, ScatterContourSeries } public class RelayCommand<T> : ICommand { #region Declarations readonly Predicate<T> _canExecute; readonly Action<T> _execute; #endregion #region Constructors /// <summary> /// Initializes a new instance of the <see cref="RelayCommand<T>"/> class and the command can always be executed. /// </summary> /// <param name="execute">The execution logic.</param> public RelayCommand(Action<T> execute) : this(execute, null) { } /// <summary> /// Initializes a new instance of the <see cref="RelayCommand<T>"/> class. /// </summary> /// <param name="execute">The execution logic.</param> /// <param name="canExecute">The execution status logic.</param> public RelayCommand(Action<T> execute, Predicate<T> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion #region ICommand Members public event EventHandler CanExecuteChanged { add { if (_canExecute != null) CommandManager.RequerySuggested += value; } remove { if (_canExecute != null) CommandManager.RequerySuggested -= value; } } [DebuggerStepThrough] public virtual Boolean CanExecute(Object parameter) { return _canExecute == null ? true : _canExecute((T)parameter); } public virtual void Execute(Object parameter) { _execute((T)parameter); } #endregion } #endregion }
Hello Guray,
I have been investigating the sample code you have sent, and it appears that most things are correct here, with the exception of a couple of issues that are preventing the ScatterContourSeries from showing. I will list these below:
1. The data you are using is not able to be triangulated correctly. The ScatterContourSeries needs this to be able to be plotted. I have added a new data source to the sample project you have provided for use with the ScatterContourSeries and it works after modifying step 2 below. You can read more about “triangular” data at our ScatterContourSeries topic, here.
2. The “ValueMemberPath” being passed to the binding is incorrect. I have modified this such that it is hard-coded for the ScatterContourSeries. The series is now added and displayed in the chart.
I have attached the sample project with the modifications above. Note, you probably were not able to attach the sample project because our forums only allow files lower than 1MB to be attached. Please delete the bin and obj folders from the sample project prior to zipping and attaching it.
Please let me know if you have any other questions or concerns on this matter.
XamDataChartOtherSeriesBinderDemo.zip
Thnak you this helps, can you also send me a script to how to add brush in the system ?
Thank you , this is exactly what I want
I have been investigating into your new requirements in this case, and I have some information for you. I am attaching a modified version of the previous sample project that implements the below. The steps taken to implement your requirements are listed below:
1a. Add Brush property to SeriesViewModel1b. Add "BindBrushIfAvailable" to SeriesBinderInfo that checks for a SeriesViewModel and binds Series.BrushProperty if it is present.1c. Call BindBrushIfAvailable after check to series != null in UpdateSeriesProperties, passing the series and SeriesViewModel to that method.
2. Check for existing NumericXAxis and NumericYAxis in _owner.Axes collection in the different "cases" of the UpdateSeriesProperties method. If one exists, reuse it.
Note, this is not currently implemented for the CategoryXAxis, as doing this will potentially be very complicated. In order to reuse a CategoryXAxis, you need to ensure that all categories belonging to the underlying series’ ItemsSource match the categories for the ItemsSource of the CategoryXAxis in both number and in value.
For example, in the case of this sample, at the start, you are adding a LineSeries and a ColumnSeries to your top chart. The LineSeries has 22 points and the ColumnSeries has 10. Both of the values of the corresponding categories are different as well. This will never work in the XamDataChart, as the ColumnSeries would need to be a date category with 22 points, matching the LineSeries. If this match doesn’t happen, the corresponding series will simply not be displayed with a “shared” CategoryXAxis. As you might imagine, implementation of this would not be simple.
5367.XamDataChartOtherSeriesBinderDemo.zip
Sorry it was not clear,
There are two more things I want to implement.First: I also want to send color information, when I add line/contour, Second: How can I prevent it to add new x and y axis when I add new line/contour
Thank you
Thank you for your update on this matter, although I’m not entirely sure I understand the requirement you are looking for? Can you please elaborate on what you are trying to do and what is meant by “how to add brush in system”?