These images are of same graph but on zoom in zoomout of graph and on stretch and compression of window. Can any body suggest some thing to fix the problem Thanks.
Message before inserting images:
Hi,
I am working on xamdatachart markers I am trying to display markers on a series based on data not to all dataponts. I am successfull to do so but I have identified some abnormal behavours. On zoomin,zoomout of chart and on compression and stretching window its appearing and dis apearing from the series. Please find attached screen shots for better insight of the issue:
Are those scatter line series or line series? Which axes are you using? How many data points are in each series? That behavior is strange, but we might need to have more information about your data to replicate. Can you provide a sample?
Thanks,
Graham
Ah, Ok,
I understand what's going on there now. One of the strategies that the data chart uses to efficiently draw large numbers of data points is to avoid drawing graphics and geometry that it doesn't have to. If you have several markers that are so close together that they would be on top of each other, it will just draw one of the markers, until you zoom in and the markers get further apart.
If you are using a LineSeries, then the chart will try to avoid selecting colliding markers, if you are using a scatter or scatter line series, there is a maximum number of markers that will be rendered defined by the MaximumMarkers property which is user adjustable, and the chart will try to select points from diverse regions of the plotting area before selecting points that are close to each other.
There is currently nothing that makes one marker more important than another to the chart, so in the first set of screenshots you posted, some of the markers that you had affixed labels to were hidden by chance, and others remained visible.
In these more recent screenshots you can see a more regular hiding of points because they are too close to the others.
It seems like what you are trying to accomplish is to mark certain markers as "Important" so that they will be gauranteed to display because they have an annotation. We don't currently have a feature built in to the chart to support this, but you could make a feature request.
Since the same feature has been requested recently in another forum post, I provided a sample to show how you could add your own point annotations that will be gauranteed to display since the series won't automatically cull them like the regular markers. See here:
http://community.infragistics.com/forums/p/46486/252956.aspx#252956
Hope this helps!
-Graham
Hi Graham,
The given code I tried to run. But its not working properly can you please add a sample application for me.
Thanks.
Here is an sample of how to approach point annotations that you might find useful:
The Xaml:
<UserControl.Resources> <local:TestData x:Key="vm" /> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <igChart:XamDataChart x:Name="chart" VerticalZoomable="True" HorizontalZoomable="True" > <local:ChartBehaviors.ChartPoints> <local:ChartPointsBehavior> <local:ChartPointsBehavior.PointTemplate> <DataTemplate> <Grid> <ContentPresenter Margin="6,6,6,6" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> </ContentPresenter> <Popup IsOpen="True" DataContext="{Binding}"> <Grid> <Path Data="M 6,6 L 17,17" Stroke="Black" StrokeThickness=".5" /> <Border Margin="12,12" Background="White" CornerRadius="5" BorderThickness=".5" BorderBrush="Black" > <TextBlock Margin="3" Text="{Binding Data.Item.Label}" /> </Border> </Grid> </Popup> </Grid> </DataTemplate> </local:ChartPointsBehavior.PointTemplate> <local:ChartPointsBehavior.Points> <local:PointInfo X="1" Y="2"> <local:PointInfo.Data> <igChart:DataContext Series="{Binding ElementName=line}" Item="{Binding ElementName=line, Path=ItemsSource[1]}"/> </local:PointInfo.Data> </local:PointInfo> <local:PointInfo X="3" Y="4"> <local:PointInfo.Data> <igChart:DataContext Series="{Binding ElementName=line}" Item="{Binding ElementName=line, Path=ItemsSource[3]}"/> </local:PointInfo.Data> </local:PointInfo> </local:ChartPointsBehavior.Points> </local:ChartPointsBehavior> </local:ChartBehaviors.ChartPoints> <igChart:XamDataChart.Axes> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{StaticResource vm}" Label="{}{Label}" /> <igChart:NumericYAxis x:Name="yAxis" /> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> <igChart:LineSeries x:Name="line" MarkerType="Triangle" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Value" ItemsSource="{StaticResource vm}"/> </igChart:XamDataChart.Series> </igChart:XamDataChart> </Grid>
And the code behind:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } } public class TestData : ObservableCollection<TestDataItem> { public TestData() { Add(new TestDataItem() { Label = "1", Value = 1 }); Add(new TestDataItem() { Label = "2", Value = 2 }); Add(new TestDataItem() { Label = "3", Value = 3 }); Add(new TestDataItem() { Label = "4", Value = 4 }); Add(new TestDataItem() { Label = "5", Value = 5 }); Add(new TestDataItem() { Label = "6", Value = 6 }); Add(new TestDataItem() { Label = "7", Value = 7 }); } } public class TestDataItem { public string Label { get; set; } public double Value { get; set; } } public class ChartBehaviors : DependencyObject { public static readonly DependencyProperty ChartPointsProperty = DependencyProperty.RegisterAttached("ChartPoints", typeof(ChartPointsBehavior), typeof(ChartBehaviors), new PropertyMetadata(null, (o, e) => CursorTooltipChanged( o as XamDataChart, e.OldValue as ChartPointsBehavior, e.NewValue as ChartPointsBehavior))); public static ChartPointsBehavior GetChartPoints( DependencyObject target) { return target.GetValue(ChartPointsProperty) as ChartPointsBehavior; } public static void SetChartPoints( DependencyObject target, ChartPointsBehavior behavior) { target.SetValue(ChartPointsProperty, behavior); } private static void CursorTooltipChanged( XamDataChart chart, ChartPointsBehavior oldValue, ChartPointsBehavior newValue) { if (chart == null) { return; } if (oldValue != null) { oldValue.OnDetach(chart); } if (newValue != null) { newValue.OnAttach(chart); } } } public class PointInfo : DependencyObject { public PointInfo() { _id = Guid.NewGuid(); X = double.NaN; Y = double.NaN; } private PointInfo(Guid id) { _id = id; X = double.NaN; Y = double.NaN; } private Guid _id; public Guid Id { get { return _id; } private set { _id = value; } } public double X { get; set; } public double Y { get; set; } public string Label { get; set; } public Brush Fill { get; set; } public DataTemplate PointTemplate { get; set; } public static readonly DependencyProperty DataProperty = DependencyProperty.Register( "Data", typeof(object), typeof(PointInfo), new PropertyMetadata(null)); public object Data { get { return GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public PointInfo Clone() { PointInfo newPointInfo = new PointInfo(_id); newPointInfo.X = X; newPointInfo.Y = Y; newPointInfo.Label = Label; newPointInfo.PointTemplate = PointTemplate; newPointInfo.Fill = Fill; BindingOperations.SetBinding( newPointInfo, PointInfo.DataProperty, new Binding("Data") { Source = this }); return newPointInfo; } } public class PointInfoCollection : ObservableCollection<PointInfo> { } public class ChartPointsBehavior : DependencyObject { public ChartPointsBehavior() { Points = new PointInfoCollection(); } public static readonly DependencyProperty StripsProperty = DependencyProperty.Register("Points", typeof(PointInfoCollection), typeof(ChartPointsBehavior), new PropertyMetadata(null, (o, e) => (o as ChartPointsBehavior) .OnPointsChanged( e.OldValue as PointInfoCollection, e.NewValue as PointInfoCollection))); public PointInfoCollection Points { get { return (PointInfoCollection)GetValue(StripsProperty); } set { SetValue(StripsProperty, value); } } public DataTemplate PointTemplate { get; set; } private void OnPointsChanged( PointInfoCollection oldValue, PointInfoCollection newValue) { if (oldValue != null) { oldValue.CollectionChanged -= Points_CollectionChanged; } if (newValue != null) { newValue.CollectionChanged += Points_CollectionChanged; } RefreshPoints(); } void Points_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e) { RefreshPoints(); } private XamDataChart _owner = null; public void OnAttach(XamDataChart chart) { if (_owner != null) { OnDetach(_owner); } _owner = chart; _owner.WindowRectChanged += Owner_WindowRectChanged; _owner.SizeChanged += Owner_SizeChanged; _owner.Axes.CollectionChanged += Axes_CollectionChanged; _owner.Axes.CollectionResetting += Axes_CollectionResetting; RefreshPoints(); } void Owner_SizeChanged(object sender, SizeChangedEventArgs e) { RefreshPoints(); } void Axes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (Axis axis in e.OldItems) { axis.SizeChanged -= Axis_SizeChanged; } } if (e.NewItems != null) { foreach (Axis axis in e.NewItems) { axis.SizeChanged += Axis_SizeChanged; } } RefreshPoints(); } void Axis_SizeChanged(object sender, SizeChangedEventArgs e) { RefreshPoints(); } void Axes_CollectionResetting(object sender, EventArgs e) { foreach (Axis axis in _owner.Axes) { axis.SizeChanged -= Axis_SizeChanged; } RefreshPoints(); } void Owner_WindowRectChanged(object sender, Infragistics.RectChangedEventArgs e) { RefreshPoints(); } private void GetAxes( out Axis xAxis, out Axis yAxis) { if (_owner == null) { xAxis = null; yAxis = null; return; } xAxis = (from axis in _owner.Axes where axis is NumericXAxis || axis is CategoryXAxis || axis is CategoryDateTimeXAxis select axis).FirstOrDefault(); yAxis = (from axis in _owner.Axes where axis is NumericYAxis select axis).FirstOrDefault(); } private Series GetSeries() { if (_owner == null) { return null; } return _owner.Series.FirstOrDefault(); } private void ClearExisting() { var series = GetSeries(); if (series == null) { return; } if (series.RootCanvas == null) { return; } var existing = from child in series.RootCanvas.Children where child is ContentControl && (child as ContentControl).Content != null && (child as ContentControl).Content is PointInfo select child; existing.ToList().ForEach( (ele) => series.RootCanvas.Children.Remove(ele)); } private void RefreshPoints() { Axis xAxis; Axis yAxis; GetAxes(out xAxis, out yAxis); if (xAxis == null || yAxis == null || xAxis.RootCanvas == null) { return; } ClearExisting(); Rect viewport = GetViewportRect(xAxis); foreach (PointInfo info in Points) { PointInfo toAdd = info.Clone(); if (double.IsNaN(toAdd.X)) { toAdd.X = viewport.Left; } else { toAdd.X = xAxis.GetScaledValue( toAdd.X, _owner.WindowRect, viewport); } if (double.IsNaN(toAdd.Y)) { toAdd.Y = viewport.Top; } else { toAdd.Y = yAxis.GetScaledValue( toAdd.Y, _owner.WindowRect, viewport); } if (toAdd.PointTemplate == null) { toAdd.PointTemplate = this.PointTemplate; } if (toAdd.PointTemplate != null && !double.IsNaN(toAdd.Y) && !double.IsInfinity(toAdd.Y) && !double.IsNaN(toAdd.X) && !double.IsInfinity(toAdd.X)) { var series = GetSeries(); if (series == null) { continue; } var pointControl = new ContentControl(); Canvas.SetZIndex(pointControl, 2000); pointControl.ContentTemplate = toAdd.PointTemplate; pointControl.Content = toAdd; series.RootCanvas.Children.Add(pointControl); pointControl.Measure( new Size( double.PositiveInfinity, double.PositiveInfinity)); Canvas.SetLeft(pointControl, toAdd.X - (pointControl.DesiredSize.Width / 2.0)); Canvas.SetTop(pointControl, toAdd.Y - (pointControl.DesiredSize.Height / 2.0)); } } } private Rect GetViewportRect( Axis axis) { double top = 0; double bottom = axis.ActualHeight; double left = 0; double right = axis.ActualWidth; double width = right - left; double height = bottom - top; if (width > 0.0 && height > 0.0) { return new Rect(left, top, width, height); } return Rect.Empty; } public void OnDetach(XamDataChart chart) { if (_owner != chart) { return; } _owner.WindowRectChanged -= Owner_WindowRectChanged; _owner.SizeChanged -= Owner_SizeChanged; _owner.Axes.CollectionChanged -= Axes_CollectionChanged; _owner.Axes.CollectionResetting -= Axes_CollectionResetting; Axis xAxis; Axis yAxis; GetAxes(out xAxis, out yAxis); if (xAxis == null || yAxis == null) { return; } ClearExisting(); _owner = null; } }
Let me know if that's what you are going for, it produces a result that looks like this:
The above presented solution is in silverlight I think. Actually I am working on WPF can you please provide me a wpf version as well.
Have you had any trouble running the above in WPF? It should run in Silverlight or WPF and if there are changes required they would be relatively minor. The above extends a user control so you could either paste the code into a user control, or change any references to user control to window, and you could name the control MainWindow rather than MainPage.
Ditto for me. Working great now. Thanks Graham!
Gary
HI Graham,
Thanks for the solution. Thats exactly what I was looking for.
Here's a version that uses a separate canvas for the annotations:Code behind:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void click_Click(object sender, RoutedEventArgs e) { (Resources["vm"] as TestData)[1].Value++; } } public class TestData : ObservableCollection<TestDataItem> { public TestData() { Add(new TestDataItem() { Label = "1", Value = 1 }); Add(new TestDataItem() { Label = "2", Value = 2 }); Add(new TestDataItem() { Label = "3", Value = 3 }); Add(new TestDataItem() { Label = "4", Value = 4 }); Add(new TestDataItem() { Label = "5", Value = 5 }); Add(new TestDataItem() { Label = "6", Value = 6 }); Add(new TestDataItem() { Label = "7", Value = 7 }); } } public class TestDataItem : INotifyPropertyChanged { private string _label; public string Label { get { return _label; } set { _label = value; RaisePropertyChanged("Label"); } } private double _value; public double Value { get { return _value; } set { _value = value; RaisePropertyChanged("Value"); } } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs(propertyName)); } } } public class ChartBehaviors : DependencyObject { public static readonly DependencyProperty ChartPointsProperty = DependencyProperty.RegisterAttached("ChartPoints", typeof(ChartPointsBehavior), typeof(ChartBehaviors), new PropertyMetadata(null, (o, e) => CursorTooltipChanged( o as XamDataChart, e.OldValue as ChartPointsBehavior, e.NewValue as ChartPointsBehavior))); public static ChartPointsBehavior GetChartPoints( DependencyObject target) { return target.GetValue(ChartPointsProperty) as ChartPointsBehavior; } public static void SetChartPoints( DependencyObject target, ChartPointsBehavior behavior) { target.SetValue(ChartPointsProperty, behavior); } private static void CursorTooltipChanged( XamDataChart chart, ChartPointsBehavior oldValue, ChartPointsBehavior newValue) { if (chart == null) { return; } if (oldValue != null) { oldValue.OnDetach(chart); } if (newValue != null) { newValue.OnAttach(chart); } } } public class PointInfo : FrameworkElement { public PointInfo() { _id = Guid.NewGuid(); X = double.NaN; Y = double.NaN; } private PointInfo(Guid id) { _id = id; X = double.NaN; Y = double.NaN; } private Guid _id; public Guid Id { get { return _id; } private set { _id = value; } } public static readonly DependencyProperty XProperty = DependencyProperty.Register( "X", typeof(double), typeof(PointInfo), new PropertyMetadata(0.0, (o, e) => (o as PointInfo).OnPropertyChanged( "X", e.OldValue, e.NewValue))); private void OnPropertyChanged(string propertyName, object oldValue, object newValue) { if (Owner != null) { Owner.SignalNeedsRefresh(); } } public double X { get { return (double)GetValue(XProperty); } set { SetValue(XProperty, value); } } public static readonly DependencyProperty YProperty = DependencyProperty.Register( "Y", typeof(double), typeof(PointInfo), new PropertyMetadata(0.0, (o, e) => (o as PointInfo).OnPropertyChanged( "Y", e.OldValue, e.NewValue))); public double Y { get { return (double)GetValue(YProperty); } set { SetValue(YProperty, value); } } public static readonly DependencyProperty LabelProperty = DependencyProperty.Register( "Label", typeof(string), typeof(PointInfo), new PropertyMetadata("", (o, e) => (o as PointInfo).OnPropertyChanged( "Label", e.OldValue, e.NewValue))); public string Label { get { return (string)GetValue(LabelProperty); } set { SetValue(LabelProperty, value); } } public Brush Fill { get; set; } public DataTemplate PointTemplate { get; set; } public static readonly DependencyProperty DataProperty = DependencyProperty.Register( "Data", typeof(object), typeof(PointInfo), new PropertyMetadata(null, (o, e) => (o as PointInfo).OnPropertyChanged( "Data", e.OldValue, e.NewValue))); public object Data { get { return GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public static readonly DependencyProperty DataIndexProperty = DependencyProperty.Register( "DataIndex", typeof(int), typeof(PointInfo), new PropertyMetadata(-1, (o, e) => (o as PointInfo).OnPropertyChanged( "DataIndex", e.OldValue, e.NewValue))); public int DataIndex { get { return (int)GetValue(DataIndexProperty); } set { SetValue(DataIndexProperty, value); } } public static readonly DependencyProperty SeriesIndexProperty = DependencyProperty.Register( "SeriesIndex", typeof(int), typeof(PointInfo), new PropertyMetadata(0, (o, e) => (o as PointInfo).OnPropertyChanged( "SeriesIndex", e.OldValue, e.NewValue))); public int SeriesIndex { get { return (int)GetValue(SeriesIndexProperty); } set { SetValue(SeriesIndexProperty, value); } } public PointInfo Clone() { PointInfo newPointInfo = new PointInfo(_id); newPointInfo.X = X; newPointInfo.Y = Y; newPointInfo.Label = Label; newPointInfo.PointTemplate = PointTemplate; newPointInfo.Fill = Fill; newPointInfo.DataIndex = DataIndex; BindingOperations.SetBinding( newPointInfo, PointInfo.DataProperty, new Binding("Data") { Source = this }); return newPointInfo; } public PointInfoCollection Owner { get; set; } } public class PointInfoCollection : ObservableCollection<PointInfo> { protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { base.OnCollectionChanged(e); if (e.OldItems != null) { foreach (var item in e.OldItems.OfType<PointInfo>()) { item.Owner = null; } } if (e.NewItems != null) { foreach (var item in e.NewItems.OfType<PointInfo>()) { item.Owner = this; } } } internal void SignalNeedsRefresh() { if (NeedsRefresh != null) { NeedsRefresh(this); } } public delegate void NeedsRefreshEventHandler(object o); public event NeedsRefreshEventHandler NeedsRefresh; } public class AnnotationCanvas : Canvas { } public class ChartPointsBehavior : DependencyObject { public ChartPointsBehavior() { Points = new PointInfoCollection(); } public static readonly DependencyProperty StripsProperty = DependencyProperty.Register("Points", typeof(PointInfoCollection), typeof(ChartPointsBehavior), new PropertyMetadata(null, (o, e) => (o as ChartPointsBehavior) .OnPointsChanged( e.OldValue as PointInfoCollection, e.NewValue as PointInfoCollection))); public PointInfoCollection Points { get { return (PointInfoCollection)GetValue(StripsProperty); } set { SetValue(StripsProperty, value); } } public DataTemplate PointTemplate { get; set; } private void OnPointsChanged( PointInfoCollection oldValue, PointInfoCollection newValue) { if (oldValue != null) { oldValue.CollectionChanged -= Points_CollectionChanged; oldValue.NeedsRefresh -= NewValue_NeedsRefresh; } if (newValue != null) { newValue.CollectionChanged += Points_CollectionChanged; newValue.NeedsRefresh += NewValue_NeedsRefresh; } RefreshPoints(); } void NewValue_NeedsRefresh(object o) { RefreshPoints(); } void Points_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e) { RefreshPoints(); } private XamDataChart _owner = null; private AnnotationCanvas _canvas = null; public void OnAttach(XamDataChart chart) { if (_owner != null) { OnDetach(_owner); } _owner = chart; _owner.WindowRectChanged += Owner_WindowRectChanged; _owner.SizeChanged += Owner_SizeChanged; _owner.Axes.CollectionChanged += Axes_CollectionChanged; _owner.Axes.CollectionResetting += Axes_CollectionResetting; RefreshPoints(); } void Owner_SizeChanged(object sender, SizeChangedEventArgs e) { RefreshPoints(); } void Axes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (Axis axis in e.OldItems) { axis.SizeChanged -= Axis_SizeChanged; } } if (e.NewItems != null) { foreach (Axis axis in e.NewItems) { axis.SizeChanged += Axis_SizeChanged; } } RefreshPoints(); } void Axis_SizeChanged(object sender, SizeChangedEventArgs e) { RefreshPoints(); } void Axes_CollectionResetting(object sender, EventArgs e) { foreach (Axis axis in _owner.Axes) { axis.SizeChanged -= Axis_SizeChanged; } RefreshPoints(); } void Owner_WindowRectChanged(object sender, Infragistics.RectChangedEventArgs e) { RefreshPoints(); } private void GetAxes( out Axis xAxis, out Axis yAxis) { if (_owner == null) { xAxis = null; yAxis = null; return; } xAxis = (from axis in _owner.Axes where axis is NumericXAxis || axis is CategoryXAxis || axis is CategoryDateTimeXAxis select axis).FirstOrDefault(); yAxis = (from axis in _owner.Axes where axis is NumericYAxis select axis).FirstOrDefault(); } private Series GetSeries() { if (_owner == null) { return null; } return _owner.Series.FirstOrDefault(); } private AnnotationCanvas GetCanvas() { if (_canvas != null) { return _canvas; } Series series = GetSeries(); if (series == null) { return null; } Panel parent = series.Parent as Panel; if (parent == null) { return null; } _canvas = new AnnotationCanvas(); Canvas.SetZIndex(_canvas, 10000); parent.Children.Add(_canvas); return _canvas; } private void RemoveCanvas() { if (_canvas == null) { return; } Panel parent = _canvas.Parent as Panel; if (parent == null) { _canvas = null; return; } parent.Children.Remove(_canvas); _canvas = null; } private void ClearExisting() { var canvas = GetCanvas(); if (canvas == null) { return; } var existing = from child in canvas.Children .OfType<ContentControl>() where child.Content != null && child.Content is PointInfo select child; existing.ToList().ForEach( (ele) => canvas.Children.Remove(ele)); } private void RefreshPoints() { Axis xAxis; Axis yAxis; GetAxes(out xAxis, out yAxis); if (xAxis == null || yAxis == null || xAxis.RootCanvas == null) { return; } ClearExisting(); Rect viewport = GetViewportRect(xAxis); foreach (PointInfo info in Points) { PointInfo toAdd = info.Clone(); if (double.IsNaN(toAdd.X)) { toAdd.X = viewport.Left; } else { toAdd.X = xAxis.GetScaledValue( toAdd.X, _owner.WindowRect, viewport); } if (double.IsNaN(toAdd.Y)) { toAdd.Y = viewport.Top; } else { toAdd.Y = yAxis.GetScaledValue( toAdd.Y, _owner.WindowRect, viewport); } if (toAdd.PointTemplate == null) { toAdd.PointTemplate = this.PointTemplate; } if (toAdd.PointTemplate != null && !double.IsNaN(toAdd.Y) && !double.IsInfinity(toAdd.Y) && !double.IsNaN(toAdd.X) && !double.IsInfinity(toAdd.X)) { var canvas = GetCanvas(); if (canvas == null) { continue; } var pointControl = new ContentControl(); Canvas.SetZIndex(pointControl, 2000); if (toAdd.DataIndex >= 0 && toAdd.SeriesIndex >= 0 && toAdd.SeriesIndex < _owner.Series.Count) { var series = _owner.Series[toAdd.SeriesIndex]; toAdd.SetBinding( PointInfo.DataProperty, new Binding( "ItemsSource[" + toAdd.DataIndex + "]") { Source = series }); } pointControl.ContentTemplate = toAdd.PointTemplate; pointControl.Content = toAdd; canvas.Children.Add(pointControl); pointControl.Measure( new Size( double.PositiveInfinity, double.PositiveInfinity)); Canvas.SetLeft(pointControl, toAdd.X - (pointControl.DesiredSize.Width / 2.0)); Canvas.SetTop(pointControl, toAdd.Y - (pointControl.DesiredSize.Height / 2.0)); } } } private Rect GetViewportRect( Axis axis) { double top = 0; double bottom = axis.ActualHeight; double left = 0; double right = axis.ActualWidth; double width = right - left; double height = bottom - top; if (width > 0.0 && height > 0.0) { return new Rect(left, top, width, height); } return Rect.Empty; } public void OnDetach(XamDataChart chart) { if (_owner != chart) { return; } _owner.WindowRectChanged -= Owner_WindowRectChanged; _owner.SizeChanged -= Owner_SizeChanged; _owner.Axes.CollectionChanged -= Axes_CollectionChanged; _owner.Axes.CollectionResetting -= Axes_CollectionResetting; Axis xAxis; Axis yAxis; GetAxes(out xAxis, out yAxis); if (xAxis == null || yAxis == null) { return; } ClearExisting(); RemoveCanvas(); _owner = null; } }
Hope this helps!-Graham
The current version of that code is storing the annotations in the first series' canvas. Which is z ordered lowest. You can change it to store them in the last series' canvas. By doing something like this:
private Series GetSeries() { if (_owner == null) { return null; } return _owner.Series.LastOrDefault(); }
You may also need to change ClearExisting to make sure it checks all series' canvases for annotations to remove if you do things that will reorder the series.
An alternate solution might be to set the ZIndex on the first series to a high number since thats were the annotations are put.
Yet another solution would be to put a new canvas in the parent that holds the series visually, give it a high ZIndex and put the annotations in that.
Does this help?
Here's one way how you might make the points more dynamic with some bindings:
<Window.Resources> <local:TestData x:Key="vm" /> </Window.Resources> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions > <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <igChart:XamDataChart x:Name="chart" VerticalZoomable="True" HorizontalZoomable="True" > <local:ChartBehaviors.ChartPoints> <local:ChartPointsBehavior> <local:ChartPointsBehavior.PointTemplate> <DataTemplate> <Grid > <Grid x:Name="container" Width="6" Height="6"/> <Canvas Width="0" Height="0"> <Path Data="M 1,1 L 17,17" Stroke="Black" StrokeThickness=".5" /> <Border Margin="12,12" Background="White" CornerRadius="5" BorderThickness=".5" BorderBrush="Black" > <TextBlock Margin="3" Text="{Binding Data.Label}" /> </Border> </Canvas> </Grid> </DataTemplate> </local:ChartPointsBehavior.PointTemplate> <local:ChartPointsBehavior.Points> <local:PointInfo X="1" Y="{Binding Source={StaticResource vm}, Path=[1].Value}" DataIndex="1"> </local:PointInfo> <local:PointInfo X="3" Y="{Binding Source={StaticResource vm}, Path=[3].Value}" DataIndex="3"> </local:PointInfo> </local:ChartPointsBehavior.Points> </local:ChartPointsBehavior> </local:ChartBehaviors.ChartPoints> <igChart:XamDataChart.Axes> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{StaticResource vm}" Label="{}{Label}" /> <igChart:NumericYAxis x:Name="yAxis" /> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> <igChart:LineSeries x:Name="line" MarkerType="Triangle" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Value" ItemsSource="{StaticResource vm}"/> </igChart:XamDataChart.Series> </igChart:XamDataChart> <Button Grid.Row="1" x:Name="click" Content="Click" Click="click_Click"/> </Grid>
Code behind:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void click_Click(object sender, RoutedEventArgs e) { (Resources["vm"] as TestData)[1].Value++; } } public class TestData : ObservableCollection<TestDataItem> { public TestData() { Add(new TestDataItem() { Label = "1", Value = 1 }); Add(new TestDataItem() { Label = "2", Value = 2 }); Add(new TestDataItem() { Label = "3", Value = 3 }); Add(new TestDataItem() { Label = "4", Value = 4 }); Add(new TestDataItem() { Label = "5", Value = 5 }); Add(new TestDataItem() { Label = "6", Value = 6 }); Add(new TestDataItem() { Label = "7", Value = 7 }); } } public class TestDataItem : INotifyPropertyChanged { private string _label; public string Label { get { return _label; } set { _label = value; RaisePropertyChanged("Label"); } } private double _value; public double Value { get { return _value; } set { _value = value; RaisePropertyChanged("Value"); } } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs(propertyName)); } } } public class ChartBehaviors : DependencyObject { public static readonly DependencyProperty ChartPointsProperty = DependencyProperty.RegisterAttached("ChartPoints", typeof(ChartPointsBehavior), typeof(ChartBehaviors), new PropertyMetadata(null, (o, e) => CursorTooltipChanged( o as XamDataChart, e.OldValue as ChartPointsBehavior, e.NewValue as ChartPointsBehavior))); public static ChartPointsBehavior GetChartPoints( DependencyObject target) { return target.GetValue(ChartPointsProperty) as ChartPointsBehavior; } public static void SetChartPoints( DependencyObject target, ChartPointsBehavior behavior) { target.SetValue(ChartPointsProperty, behavior); } private static void CursorTooltipChanged( XamDataChart chart, ChartPointsBehavior oldValue, ChartPointsBehavior newValue) { if (chart == null) { return; } if (oldValue != null) { oldValue.OnDetach(chart); } if (newValue != null) { newValue.OnAttach(chart); } } } public class PointInfo : FrameworkElement { public PointInfo() { _id = Guid.NewGuid(); X = double.NaN; Y = double.NaN; } private PointInfo(Guid id) { _id = id; X = double.NaN; Y = double.NaN; } private Guid _id; public Guid Id { get { return _id; } private set { _id = value; } } public static readonly DependencyProperty XProperty = DependencyProperty.Register( "X", typeof(double), typeof(PointInfo), new PropertyMetadata(0.0, (o, e) => (o as PointInfo).OnPropertyChanged( "X", e.OldValue, e.NewValue))); private void OnPropertyChanged(string propertyName, object oldValue, object newValue) { if (Owner != null) { Owner.SignalNeedsRefresh(); } } public double X { get { return (double)GetValue(XProperty); } set { SetValue(XProperty, value); } } public static readonly DependencyProperty YProperty = DependencyProperty.Register( "Y", typeof(double), typeof(PointInfo), new PropertyMetadata(0.0, (o, e) => (o as PointInfo).OnPropertyChanged( "Y", e.OldValue, e.NewValue))); public double Y { get { return (double)GetValue(YProperty); } set { SetValue(YProperty, value); } } public static readonly DependencyProperty LabelProperty = DependencyProperty.Register( "Label", typeof(string), typeof(PointInfo), new PropertyMetadata("", (o, e) => (o as PointInfo).OnPropertyChanged( "Label", e.OldValue, e.NewValue))); public string Label { get { return (string)GetValue(LabelProperty); } set { SetValue(LabelProperty, value); } } public Brush Fill { get; set; } public DataTemplate PointTemplate { get; set; } public static readonly DependencyProperty DataProperty = DependencyProperty.Register( "Data", typeof(object), typeof(PointInfo), new PropertyMetadata(null, (o, e) => (o as PointInfo).OnPropertyChanged( "Data", e.OldValue, e.NewValue))); public object Data { get { return GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public static readonly DependencyProperty DataIndexProperty = DependencyProperty.Register( "DataIndex", typeof(int), typeof(PointInfo), new PropertyMetadata(-1, (o, e) => (o as PointInfo).OnPropertyChanged( "DataIndex", e.OldValue, e.NewValue))); public int DataIndex { get { return (int)GetValue(DataIndexProperty); } set { SetValue(DataIndexProperty, value); } } public PointInfo Clone() { PointInfo newPointInfo = new PointInfo(_id); newPointInfo.X = X; newPointInfo.Y = Y; newPointInfo.Label = Label; newPointInfo.PointTemplate = PointTemplate; newPointInfo.Fill = Fill; newPointInfo.DataIndex = DataIndex; BindingOperations.SetBinding( newPointInfo, PointInfo.DataProperty, new Binding("Data") { Source = this }); return newPointInfo; } public PointInfoCollection Owner { get; set; } } public class PointInfoCollection : ObservableCollection<PointInfo> { protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { base.OnCollectionChanged(e); if (e.OldItems != null) { foreach (var item in e.OldItems.OfType<PointInfo>()) { item.Owner = null; } } if (e.NewItems != null) { foreach (var item in e.NewItems.OfType<PointInfo>()) { item.Owner = this; } } } internal void SignalNeedsRefresh() { if (NeedsRefresh != null) { NeedsRefresh(this); } } public delegate void NeedsRefreshEventHandler(object o); public event NeedsRefreshEventHandler NeedsRefresh; } public class ChartPointsBehavior : DependencyObject { public ChartPointsBehavior() { Points = new PointInfoCollection(); } public static readonly DependencyProperty StripsProperty = DependencyProperty.Register("Points", typeof(PointInfoCollection), typeof(ChartPointsBehavior), new PropertyMetadata(null, (o, e) => (o as ChartPointsBehavior) .OnPointsChanged( e.OldValue as PointInfoCollection, e.NewValue as PointInfoCollection))); public PointInfoCollection Points { get { return (PointInfoCollection)GetValue(StripsProperty); } set { SetValue(StripsProperty, value); } } public DataTemplate PointTemplate { get; set; } private void OnPointsChanged( PointInfoCollection oldValue, PointInfoCollection newValue) { if (oldValue != null) { oldValue.CollectionChanged -= Points_CollectionChanged; oldValue.NeedsRefresh -= NewValue_NeedsRefresh; } if (newValue != null) { newValue.CollectionChanged += Points_CollectionChanged; newValue.NeedsRefresh += NewValue_NeedsRefresh; } RefreshPoints(); } void NewValue_NeedsRefresh(object o) { RefreshPoints(); } void Points_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e) { RefreshPoints(); } private XamDataChart _owner = null; public void OnAttach(XamDataChart chart) { if (_owner != null) { OnDetach(_owner); } _owner = chart; _owner.WindowRectChanged += Owner_WindowRectChanged; _owner.SizeChanged += Owner_SizeChanged; _owner.Axes.CollectionChanged += Axes_CollectionChanged; _owner.Axes.CollectionResetting += Axes_CollectionResetting; RefreshPoints(); } void Owner_SizeChanged(object sender, SizeChangedEventArgs e) { RefreshPoints(); } void Axes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (Axis axis in e.OldItems) { axis.SizeChanged -= Axis_SizeChanged; } } if (e.NewItems != null) { foreach (Axis axis in e.NewItems) { axis.SizeChanged += Axis_SizeChanged; } } RefreshPoints(); } void Axis_SizeChanged(object sender, SizeChangedEventArgs e) { RefreshPoints(); } void Axes_CollectionResetting(object sender, EventArgs e) { foreach (Axis axis in _owner.Axes) { axis.SizeChanged -= Axis_SizeChanged; } RefreshPoints(); } void Owner_WindowRectChanged(object sender, Infragistics.RectChangedEventArgs e) { RefreshPoints(); } private void GetAxes( out Axis xAxis, out Axis yAxis) { if (_owner == null) { xAxis = null; yAxis = null; return; } xAxis = (from axis in _owner.Axes where axis is NumericXAxis || axis is CategoryXAxis || axis is CategoryDateTimeXAxis select axis).FirstOrDefault(); yAxis = (from axis in _owner.Axes where axis is NumericYAxis select axis).FirstOrDefault(); } private Series GetSeries() { if (_owner == null) { return null; } return _owner.Series.FirstOrDefault(); } private void ClearExisting() { var series = GetSeries(); if (series == null) { return; } if (series.RootCanvas == null) { return; } var existing = from child in series.RootCanvas.Children .OfType<ContentControl>() where child.Content != null && child.Content is PointInfo select child; existing.ToList().ForEach( (ele) => series.RootCanvas.Children.Remove(ele)); } private void RefreshPoints() { Axis xAxis; Axis yAxis; GetAxes(out xAxis, out yAxis); if (xAxis == null || yAxis == null || xAxis.RootCanvas == null) { return; } ClearExisting(); Rect viewport = GetViewportRect(xAxis); foreach (PointInfo info in Points) { PointInfo toAdd = info.Clone(); if (double.IsNaN(toAdd.X)) { toAdd.X = viewport.Left; } else { toAdd.X = xAxis.GetScaledValue( toAdd.X, _owner.WindowRect, viewport); } if (double.IsNaN(toAdd.Y)) { toAdd.Y = viewport.Top; } else { toAdd.Y = yAxis.GetScaledValue( toAdd.Y, _owner.WindowRect, viewport); } if (toAdd.PointTemplate == null) { toAdd.PointTemplate = this.PointTemplate; } if (toAdd.PointTemplate != null && !double.IsNaN(toAdd.Y) && !double.IsInfinity(toAdd.Y) && !double.IsNaN(toAdd.X) && !double.IsInfinity(toAdd.X)) { var series = GetSeries(); if (series == null) { continue; } var pointControl = new ContentControl(); Canvas.SetZIndex(pointControl, 2000); if (toAdd.DataIndex >= 0) { toAdd.SetBinding( PointInfo.DataProperty, new Binding( "ItemsSource[" + toAdd.DataIndex + "]") { Source = series }); } pointControl.ContentTemplate = toAdd.PointTemplate; pointControl.Content = toAdd; series.RootCanvas.Children.Add(pointControl); pointControl.Measure( new Size( double.PositiveInfinity, double.PositiveInfinity)); Canvas.SetLeft(pointControl, toAdd.X - (pointControl.DesiredSize.Width / 2.0)); Canvas.SetTop(pointControl, toAdd.Y - (pointControl.DesiredSize.Height / 2.0)); } } } private Rect GetViewportRect( Axis axis) { double top = 0; double bottom = axis.ActualHeight; double left = 0; double right = axis.ActualWidth; double width = right - left; double height = bottom - top; if (width > 0.0 && height > 0.0) { return new Rect(left, top, width, height); } return Rect.Empty; } public void OnDetach(XamDataChart chart) { if (_owner != chart) { return; } _owner.WindowRectChanged -= Owner_WindowRectChanged; _owner.SizeChanged -= Owner_SizeChanged; _owner.Axes.CollectionChanged -= Axes_CollectionChanged; _owner.Axes.CollectionResetting -= Axes_CollectionResetting; Axis xAxis; Axis yAxis; GetAxes(out xAxis, out yAxis); if (xAxis == null || yAxis == null) { return; } ClearExisting(); _owner = null; } }