In the XamDataChart We want to be able to only display the marker to which the mouse's x-coordinate is closest to. In other words, we want to visually track the current x-location along the chart.
This idea is similar to Google Finance. It displays a circle on the chart.
In the attached picture, obviously three circles are displayed - one for each stock.
Hope this makes sense. Thanks in advance.
Adam
Adam,
Here's one way of approaching this. Hope it helps you out! The xaml:
<UserControl.Resources> <local:TestData x:Key="data1" /> <local:TestData x:Key="data2" /> <DataTemplate x:Key="customTemplate"> <Grid> <Grid.Resources> <local:EqualChecker x:Key="checker" Value1="{Binding Path=Series.(local:SeriesExtensions.CrosshairItem)}" Value2="{Binding Path=Item}"/> <local:VisibilityConverter x:Key="converter" /> <local:VisibleItemsRegisterer x:Key="registerer" Item="{Binding Path=Item}" /> </Grid.Resources> <Ellipse Stretch="Fill" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="Transparent" Stroke="Transparent" StrokeThickness="0.5" MinWidth="10" MinHeight="10" > </Ellipse> <Ellipse Stretch="Fill" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{Binding ActualItemBrush}" Stroke="{Binding Series.ActualMarkerOutline}" StrokeThickness="0.5" MinWidth="10" MinHeight="10" Visibility="{Binding Path=Result, Source={StaticResource checker}, Converter={StaticResource converter}}" > </Ellipse> </Grid> </DataTemplate> <Style x:Key="crosshairStyle" TargetType="Line"> <Style.Setters> <Setter Property="Visibility" Value="Collapsed" /> </Style.Setters> </Style> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <ig:XamDataChart Name="xamDataChart1" CrosshairVisibility="Visible" CrosshairLineStyle="{StaticResource crosshairStyle}" SeriesCursorMouseMove="xamDataChart1_SeriesCursorMouseMove"> <ig:XamDataChart.Axes> <ig:CategoryXAxis x:Name="xAxis" ItemsSource="{StaticResource data1}" /> <ig:NumericYAxis x:Name="yAxis" /> </ig:XamDataChart.Axes> <ig:XamDataChart.Series> <ig:LineSeries x:Name="testLine" ItemsSource="{StaticResource data1}" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Value" MarkerTemplate="{StaticResource customTemplate}"/> <ig:LineSeries x:Name="testLine2" ItemsSource="{StaticResource data2}" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Value" MarkerTemplate="{StaticResource customTemplate}"/> </ig:XamDataChart.Series> </ig:XamDataChart> </Grid>
And the code behind:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } private void xamDataChart1_SeriesCursorMouseMove( object sender, Infragistics.Controls.Charts.ChartCursorEventArgs e) { if (e.Item != null && e.Series != null && VisibleItemsRegisterer.ItemVisible(e.Item)) { SeriesExtensions.SetCrosshairItem( e.Series, e.Item); } } } public class SeriesExtensions { public static readonly DependencyProperty CrosshairItemProperty = DependencyProperty.RegisterAttached( "CrosshairItem", typeof(object), typeof(SeriesExtensions), new PropertyMetadata(null)); public static object GetCrosshairItem( DependencyObject target) { return target.GetValue(CrosshairItemProperty); } public static void SetCrosshairItem( DependencyObject target, object value) { target.SetValue(CrosshairItemProperty, value); } } public class VisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (targetType == typeof(Visibility) && value is bool) { if ((bool)value) { return Visibility.Visible; } } return Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } public class VisibleItemsRegisterer : DependencyObject { private static Dictionary<object, object> _items = new Dictionary<object, object>(); public static bool ItemVisible(object item) { return _items.ContainsKey(item); } public static readonly DependencyProperty ItemProperty = DependencyProperty.Register( "Item", typeof(object), typeof(VisibleItemsRegisterer), new PropertyMetadata(null, (o, e) => (o as VisibleItemsRegisterer).OnItemChanged(e))); public object Item { get { return GetValue(ItemProperty); } set { SetValue(ItemProperty, value); } } private void OnItemChanged( DependencyPropertyChangedEventArgs e) { if (e.OldValue != null && _items.ContainsKey(e.OldValue)) { _items.Remove(e.OldValue); } if (e.NewValue != null && !_items.ContainsKey(e.NewValue)) { _items.Add(e.NewValue, e.NewValue); } } } public class EqualChecker : DependencyObject { public static readonly DependencyProperty Value1Property = DependencyProperty.Register( "Value1", typeof(object), typeof(EqualChecker), new PropertyMetadata(null, (o, e) => (o as EqualChecker).OnValue1Changed(e))); public object Value1 { get { return GetValue(Value1Property); } set { SetValue(Value1Property, value); } } private void OnValue1Changed( DependencyPropertyChangedEventArgs e) { Result = Value1 == Value2; } public static readonly DependencyProperty Value2Property = DependencyProperty.Register( "Value2", typeof(object), typeof(EqualChecker), new PropertyMetadata(null, (o, e) => (o as EqualChecker).OnValue2Changed(e))); public object Value2 { get { return GetValue(Value2Property); } set { SetValue(Value2Property, value); } } private void OnValue2Changed( DependencyPropertyChangedEventArgs e) { Result = Value1 == Value2; } public static readonly DependencyProperty ResultProperty = DependencyProperty.Register( "Result", typeof(object), typeof(EqualChecker), new PropertyMetadata(null, (o, e) => { })); public object Result { get { return GetValue(ResultProperty); } set { SetValue(ResultProperty, value); } } } public class TestDataItem { public string Label { get; set; } public double Value { get; set; } } public class TestData : ObservableCollection<TestDataItem> { private static Random rand = new Random(); public TestData() { double curr = 0; for (int i = 0; i < 1000; i++) { if (rand.NextDouble() > .5) { curr += rand.NextDouble(); } else { curr -= rand.NextDouble(); } Add( new TestDataItem() { Label = i.ToString(), Value = curr }); } } }
Let me know if you have any questions.-Graham
Thank you for the answer. I will try it out when I get a chance. It appears more elegant than the solution that I used. I added a "dot" marker to the Series.RootCanvas.Children, and am positioning it on the canvas by using the GetScaledValue and GetUnscaledValue methods. Thank you!