Hi,
I have one instance of each of these controls bound to the same collection of items. I have the XamDataGrid ActiveDataItem bound to a property on my CodeBehind, this all works great.
How do I sync the selected item to show the same item selected in the chart? I can't find any kind of SelectedItem semantics on the series or the chart ...
if you do a using System.Linq, all should be well ;-)
Sorry, I't this one isn't it?!
http://msdn.microsoft.com/en-us/library/bb360913.aspx
That's fantastic Graham, much appreciated. Only problem is i:m not sure what to put in the OfType<object> extension method in order to get the sample working ...
Is this a Utility in Ig or some more custom code as part of this sample?
Features get ranked by priority, so if this is on the backlog and hasn't been implemented then its because other things have ended up being ranked higher for implementation. I'd encourage you to make a feature request to ensure that this is on the backlog, and so that priorities can be adjusted to accomodate its implemenation.
In the meantime, you may find this sample helpful. It implements multi selectable markers and has the added benefit that it should still display a marker, if selected even if it were to be hidden due to occlusion.
Its designed against the 11.2 version, which has a few useful events. If you are on a previous version and can't switch to 11.2 let me know and I'll see if I can adjust it for an earlier version:
<Window.Resources> <local:TestData x:Key="data" /> <DataTemplate x:Key="SelectedCircleMarkerTemplate"> <Path Stretch="Fill" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="Orange" Stroke="Gray" StrokeThickness="0.5"> <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigure StartPoint="0,0" > <PathFigure.Segments> <ArcSegment Size="4,4" RotationAngle="0" IsLargeArc="True" SweepDirection="Clockwise" Point="0,1"/> <ArcSegment Size="4,4" RotationAngle="0" IsLargeArc="False" SweepDirection="Clockwise" Point="0,0"/> </PathFigure.Segments> </PathFigure > </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path> </DataTemplate> </Window.Resources> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <igChart:XamDataChart x:Name="theChart" VerticalZoomable="True" HorizontalZoomable="True"> <igChart:XamDataChart.Axes> <igChart:NumericYAxis x:Name="yAxis" /> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{StaticResource data}" Label="{}{Label}"/> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> <igChart:LineSeries x:Name="series" ItemsSource="{StaticResource data}" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Value" > <local:ChartBehaviors.SeriesSelectionManager> <local:SeriesSelectionManager AdornmentSurface="{Binding AdornmentLayer}" SelectedItems="{Binding Source={StaticResource data}, Path=SelectedItems}" SelectionTemplate="{StaticResource SelectedCircleMarkerTemplate}" /> </local:ChartBehaviors.SeriesSelectionManager> </igChart:LineSeries> </igChart:XamDataChart.Series> </igChart:XamDataChart> <Canvas x:Name="adornment" IsHitTestVisible="False" ClipToBounds="False"/> <Button x:Name="toggleSelect" Content="Toggle B" Click="toggleSelect_Click" Grid.Row="1"/> </Grid>
And code behind:
public partial class MainWindow : Window, INotifyPropertyChanged { public MainWindow() { InitializeComponent(); AdornmentLayer = adornment; this.DataContext = this; } private Canvas _adornmentLayer; public Canvas AdornmentLayer { get { return _adornmentLayer; } set { _adornmentLayer = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs( "AdornmentLayer")); } } } public event PropertyChangedEventHandler PropertyChanged; private void toggleSelect_Click(object sender, RoutedEventArgs e) { var data = (TestData)Resources["data"]; if (data.SelectedItems.Contains(data[1])) { data.SelectedItems.Remove(data[1]); } else { data.SelectedItems.Add(data[1]); } } } public class TestData : ObservableCollection<TestDataItem> { private ObservableCollection<object> _selectedItems = null; public ObservableCollection<object> SelectedItems { get { return _selectedItems; } set { _selectedItems = value; OnPropertyChanged( new PropertyChangedEventArgs("SelectedItems")); } } public TestData() { SelectedItems = new ObservableCollection<object>(); Add(new TestDataItem() { Label = "A", Value = 1 }); Add(new TestDataItem() { Label = "B", Value = 2 }); Add(new TestDataItem() { Label = "C", Value = 3 }); Add(new TestDataItem() { Label = "D", Value = 4 }); } } public class TestDataItem { public string Label { get; set; } public double Value { get; set; } } public static class ChartBehaviors { public static SeriesSelectionManager GetSeriesSelectionManager(DependencyObject obj) { return (SeriesSelectionManager)obj.GetValue(SeriesSelectionManagerProperty); } public static void SetSeriesSelectionManager(DependencyObject obj, SeriesSelectionManager value) { obj.SetValue(SeriesSelectionManagerProperty, value); } public static readonly DependencyProperty SeriesSelectionManagerProperty = DependencyProperty.RegisterAttached( "SeriesSelectionManager", typeof(SeriesSelectionManager), typeof(ChartBehaviors), new PropertyMetadata(null, (o, e) => OnChartSelectionManagerChanged(o, (SeriesSelectionManager)e.OldValue, (SeriesSelectionManager)e.NewValue))); private static void OnChartSelectionManagerChanged( DependencyObject o, SeriesSelectionManager oldValue, SeriesSelectionManager newValue) { var series = o as Series; if (series == null) { return; } if (oldValue != null) { oldValue.DataContext = null; oldValue.OnDetach(series); } if (newValue != null) { series.SetBinding(ContextBridgeProperty, new Binding()); newValue.OnAttach(series); } } public static DependencyProperty ContextBridgeProperty = DependencyProperty.RegisterAttached("ContextBridge", typeof(object), typeof(ChartBehaviors), new PropertyMetadata(null, (o, e) => { SeriesSelectionManager ssm = GetSeriesSelectionManager(o); if (ssm != null) { ssm.DataContext = e.NewValue; } })); public static void SetContextBridge( DependencyObject target, object context) { target.SetValue(ContextBridgeProperty, context); } public static object GetContextBridge( DependencyObject target) { return target.GetValue(ContextBridgeProperty); } } public class SeriesSelectionManager : FrameworkElement { public SeriesSelectionManager() { _selectionMarkers = new Pool<ContentControl>() { Activate = MarkerActivate, Disactivate = MarkerDisactivate, Create = MarkerCreate, Destroy = MarkerDestroy }; } private void MarkerActivate(ContentControl c) { c.Visibility = Visibility.Visible; } private void MarkerDisactivate(ContentControl c) { c.Visibility = Visibility.Collapsed; } private void MarkerDestroy(ContentControl c) { c.Content = null; c.ClearValue(ContentControl.ContentTemplateProperty); var panel = c.Parent as Panel; if (panel != null && panel.Children.Contains(c)) { panel.Children.Remove(c); } } private ContentControl MarkerCreate() { ContentControl c = new ContentControl(); c.SetBinding( ContentControl.ContentTemplateProperty, new Binding() { Source = this, Path = new PropertyPath("SelectionTemplate") }); c.Visibility = Visibility.Collapsed; if (_adornmentSurface != null) { _adornmentSurface.Children.Add(c); } return c; } private Pool<ContentControl> _selectionMarkers; private bool _adorning = false; private bool _attached = false; private Canvas _adornmentSurface = null; private Series _attachedSeries = null; private SeriesViewer _attachedChart = null; public void OnAttach(Series series) { _attachedSeries = series; _attached = true; BeginAdorning(); OnAttachedChartChanged(_attachedChart, _attachedSeries.SeriesViewer); _attachedSeries.PropertyUpdated += _attachedSeries_PropertyUpdated; RefreshVisuals(); } public void OnDetach(Series series) { _attached = false; EndAdorning(); OnAttachedChartChanged(_attachedChart, null); _attachedSeries.PropertyUpdated += _attachedSeries_PropertyUpdated; _attachedSeries = null; RefreshVisuals(); } void _attachedSeries_PropertyUpdated(object sender, PropertyUpdatedEventArgs e) { if (e.PropertyName == "SeriesViewer") { OnAttachedChartChanged(_attachedChart, (SeriesViewer)e.NewValue); } } private void OnAttachedChartChanged( SeriesViewer oldSeries, SeriesViewer newSeries) { if (oldSeries != null) { oldSeries.RefreshCompleted -= ChartRefreshCompleted; oldSeries.SeriesMouseLeftButtonUp -= SeriesMouseLeftButtonUp; } if (newSeries != null) { newSeries.RefreshCompleted += ChartRefreshCompleted; newSeries.SeriesMouseLeftButtonUp += SeriesMouseLeftButtonUp; } _attachedChart = newSeries; RefreshVisuals(); } void SeriesMouseLeftButtonUp(object sender, DataChartMouseButtonEventArgs e) { if (e.Series == _attachedSeries) { if (SelectedItems.Contains(e.Item)) { SelectedItems.Remove(e.Item); } else { SelectedItems.Add(e.Item); } } } void ChartRefreshCompleted(object sender, EventArgs e) { RefreshVisuals(); } private void RefreshVisuals() { if (SelectedItems == null || SelectedItems.Count == 0 || _attachedSeries == null || _attachedChart == null || _adornmentSurface == null) { _selectionMarkers.Count = 0; return; } int count = 0; foreach (var item in SelectedItems) { double xValue = GetXValue(item); double yValue = GetYValue(item); var marker = _selectionMarkers[count]; marker.Visibility = Visibility.Visible; marker.Content = item; marker.Measure( new Size( double.PositiveInfinity, double.PositiveInfinity)); Point mappedPoint = _attachedSeries .TransformToVisual(_adornmentSurface) .Transform(new Point(xValue, yValue)); Point offsetPoint = new Point( mappedPoint.X - marker.DesiredSize.Width / 2.0, mappedPoint.Y - marker.DesiredSize.Height / 2.0); Canvas.SetLeft( marker, offsetPoint.X); Canvas.SetTop( marker, offsetPoint.Y); if (xValue < _attachedChart.ViewportRect.Left || xValue > _attachedChart.ViewportRect.Right) { marker.Visibility = System.Windows.Visibility.Collapsed; } if (yValue < _attachedChart.ViewportRect.Top || yValue > _attachedChart.ViewportRect.Bottom) { marker.Visibility = System.Windows.Visibility.Collapsed; } count++; } _selectionMarkers.Count = count; } private double GetYValue(object item) { if (_attachedSeries is AnchoredCategorySeries) { var memberPath = ((AnchoredCategorySeries)_attachedSeries).ValueMemberPath; var value = GetValueFromItem(item, memberPath); var axis = ((AnchoredCategorySeries)_attachedSeries).YAxis; return axis.ScaleValue(value); } else if (_attachedSeries is ScatterBase) { var memberPath = ((ScatterBase)_attachedSeries).YMemberPath; var value = GetValueFromItem(item, memberPath); var axis = ((ScatterBase)_attachedSeries).YAxis; return axis.ScaleValue(value); } else { //TODO: others here throw new NotImplementedException(); } } private double GetXValue(object item) { if (_attachedSeries is AnchoredCategorySeries) { var index = GetIndexOfItem(item); var axis = ((AnchoredCategorySeries)_attachedSeries).XAxis; return axis.ScaleValue(index); } else if (_attachedSeries is ScatterBase) { var memberPath = ((ScatterBase)_attachedSeries).XMemberPath; var value = GetValueFromItem(item, memberPath); var axis = ((ScatterBase)_attachedSeries).XAxis; return axis.ScaleValue(value); } else { //TODO: others here throw new NotImplementedException(); } } private int GetIndexOfItem(object item) { if (_attachedSeries.ItemsSource is IList) { return ((IList)_attachedSeries.ItemsSource).IndexOf(item); } var match = _attachedSeries.ItemsSource.OfType<object>() .Select( (theItem, i) => { return new Tuple<object, int>(theItem, i); }) .Where( (theItem) => { return theItem == item; }); var index = match.FirstOrDefault(); if (index != null) { return index.Item2; } return -1; } private double GetValueFromItem(object item, string memberPath) { var propInfo = item.GetType().GetProperty(memberPath); if (propInfo == null) { return 0; } return Convert.ToDouble( propInfo.GetValue( item, null)); } private void BeginAdorning() { if (_attached && !_adorning && AdornmentSurface != null) { _adornmentSurface = AdornmentSurface; _selectionMarkers.Count = 0; } } private void EndAdorning() { if (!_attached && _adorning && _adornmentSurface != null) { _selectionMarkers.Count = 0; _adornmentSurface = null; } } public ObservableCollection<object> SelectedItems { get { return (ObservableCollection<object>)GetValue(SelectedItemProperty); } set { SetValue(SelectedItemProperty, value); } } public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register( "SelectedItems", typeof(ObservableCollection<object>), typeof(SeriesSelectionManager), new PropertyMetadata(null, (o, e) => ((SeriesSelectionManager)o) .OnSelectedItemsChanged( (ObservableCollection<object>)e.OldValue, (ObservableCollection<object>)e.NewValue))); private void OnSelectedItemsChanged( ObservableCollection<object> oldValue, ObservableCollection<object> newValue) { if (oldValue != null) { oldValue.CollectionChanged -= SelectedItems_CollectionChanged; } if (newValue != null) { newValue.CollectionChanged += SelectedItems_CollectionChanged; } RefreshVisuals(); } void SelectedItems_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e) { RefreshVisuals(); } public DataTemplate SelectionTemplate { get { return (DataTemplate)GetValue(SelectionTemplateProperty); } set { SetValue(SelectionTemplateProperty, value); } } public static readonly DependencyProperty SelectionTemplateProperty = DependencyProperty.Register( "SelectionTemplate", typeof(DataTemplate), typeof(SeriesSelectionManager), new PropertyMetadata(null, (o, e) => ((SeriesSelectionManager)o) .OnSelectionTemplateChanged((DataTemplate)e.OldValue, (DataTemplate)e.NewValue))); private void OnSelectionTemplateChanged(DataTemplate oldValue, DataTemplate newValue) { } public Canvas AdornmentSurface { get { return (Canvas)GetValue(AdornmentSurfaceProperty); } set { SetValue(AdornmentSurfaceProperty, value); } } public static readonly DependencyProperty AdornmentSurfaceProperty = DependencyProperty.Register( "AdornmentSurface", typeof(Canvas), typeof(SeriesSelectionManager), new PropertyMetadata(null, (o, e) => ((SeriesSelectionManager)o) .OnAdornmentSurfaceChanged( (Canvas)e.OldValue, (Canvas)e.NewValue))); private void OnAdornmentSurfaceChanged( Canvas oldValue, Canvas newValue) { EndAdorning(); _adornmentSurface = newValue; BeginAdorning(); RefreshVisuals(); } }
Hope this helps!-Graham
Hi Graham,
Indeed, I've started to play around with the same approach.
void XamDataChartSeriesMouseLeftButtonDown(object sender, Infragistics.Controls.Charts.DataChartMouseButtonEventArgs e) { if (e.OriginalSource.GetType() != typeof(Ellipse)) { return; } //Get the selected Item as MonthlyCharges MonthlyCharges charge = e.Item as MonthlyCharges; if (charge == null) { return; } SelectedMonthlyCharge = charge; string value = charge.Month; Shape x = e.OriginalSource as Shape; if (_origBrush == null) { _origBrush = x.Fill; } if (x != null) { if (_lastPoint != null) { _lastPoint.Fill = _origBrush; } x.Fill = new SolidColorBrush(Colors.Cyan); _lastPoint = x; } }
Not particularly nice all in all. Why would these features be left out of the chart? I'm a position where I need to implement this in both directions, from the grid to the chart and from the chart to the grid ...
I guess I'm going to have to implement my own multi select logic on the chart ... ouch ...