If there are multiple players on a chart is it possible to have a unified tooltip that displays the current Y value of each layer currently in contact with the vertical crosshair without having to have the cursor touching each layer individually?
You can adapt the code from this post: https://es.infragistics.com/community/forums/f/retired-products-and-controls/39579/xamwebdatachart---crosshairs-position
to display the crosshair value for each series in the chart. The key would be to use the assigned x and y axis for each series to map from the world coordinate points to the axis values for each set of axes. Once you have resolved the values for each series, just store them somewhere that you can bind to from the tooltip. Would you like more info, or does this put you on the right path?
-Graham
With the release bits is there a more elegant solution to this or shold I still use the code from your previous post? In the post you linked to you indicated that the initial release did not have the bits to make this elegant.
Actually any example you have would be helpful. Since I am adding all series, axes, etc. dynamically from code I have difficulties getting the standard functionality to work much less something a bit more advanced like this.
Thanks!Mike
I'm personally unable to discuss product roadmaps and timeframes, but you may want to shoot an email over to productmanager@infragistics.com. In the meantime, if you have more questions about how to implement the functionality you are going for on the current codebase, we'll see if we can assist.
Here would be a way of converting that interpolated categoryxaxis axis value to date times. It assumes the collection bound to the chart is of type TestData, and that the items have a property called Date of type DateTime.
This is a modification of the end of that function in the earlier linked code. Note there is a .5 offset of the axis value to make the crosshair x align with the category labels, rather than with the category major grid lines. You can comment out that line if you want it to align with the major grid lines instead:
//interpolate axis values. double xAxisValue = left + (windowX * (right - left)); double yAxisValue = top + (windowY * (bottom - top)); TestData coll = x.ItemsSource as TestData; //adjust this as to whether you want the crosshair values //to align with the labels or the major gridlines. xAxisValue -= .5; if (xAxisValue >= 0 && !double.IsNaN(xAxisValue)) { int floorXIndex = (int) Math.Floor(xAxisValue); int ceilingXIndex = Math.Min((int) Math.Ceiling(xAxisValue), coll.Count - 1); double q = xAxisValue - (double) floorXIndex; long xFloorTicks = coll[floorXIndex].Date.Ticks; long xCeilingTicks = coll[ceilingXIndex].Date.Ticks; long xInterpolatedTicks = (long) (xFloorTicks + q*(xCeilingTicks - xFloorTicks)); XValue = new DateTime(xInterpolatedTicks); } if (!double.IsNaN(yAxisValue) && !double.IsInfinity(yAxisValue)) { YValue = yAxisValue; }
Ah I see. There is a good bit of interpolation going on to go from crosshair point to series xaxis value. :)
So the next question might be more of a silverlight general question but in order to display the tooltip it appears that the mouse must be intersecting one of the series lines. Is there any way to have a more generic tooltip that displays all series y values given the current x value of the pointer? If not I haven't seen an example of binding the tooltip to anything other than the datacontext in the xaml directly. Do you have anything close to what I am trying to do here? I will need to add the tooltip at the same time I add the series. I have some code that adds the tooltip and uses a controltemplate but I get no values, just the line color and marker.
If you want access to the closest y value under the cursor (crosshair) you should experiment with SeriesCursorMouseMove, which provides you with the requisite item. You will get events from each series. This won't be an interpolated y value though, if that's what you are looking for. It will give you the actual item from your data source that the mouse is near x-wise. I'll see if I can provide an example of leveraging this in a tooltip. To get an interpolated y value, you'd have to get a bit craftier with something similar to the above code.
Mike,
See how something like this works out for you:
The Xaml:
<UserControl.Resources> <local:TestData x:Key="data" /> <local:TestData2 x:Key="data2" /> <DataTemplate x:Key="tooltipTemplate"> <Border BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Background="White" IsHitTestVisible="False" Padding="5"> <ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <ContentPresenter Content="{Binding}" ContentTemplate="{Binding Series.LegendItemBadgeTemplate}" /> <TextBlock Text="Series: " /> <TextBlock Text="{Binding Series.Title}" /> <TextBlock Text=" " /> <TextBlock Text="Value: " /> <TextBlock Text="{Binding Item.Value}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Border> </DataTemplate> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <igChart:XamDataChart SeriesCursorMouseMove="XamDataChart_SeriesCursorMouseMove" CrosshairVisibility="Visible"> <local:ChartBehaviors.CursorTooltip> <local:CursorTooltipBehavior TooltipTemplate="{StaticResource tooltipTemplate}" /> </local:ChartBehaviors.CursorTooltip> <igChart:XamDataChart.Axes> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{StaticResource data}" Label="{}{Date}" > </igChart:CategoryXAxis> <igChart:NumericYAxis x:Name="yAxis" /> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> <igChart:ColumnSeries x:Name="columnSeries" Title="Column 1" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ItemsSource="{StaticResource data}" ValueMemberPath="Value" /> <igChart:ColumnSeries x:Name="columnSeries2" Title="Column 2" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ItemsSource="{StaticResource data2}" ValueMemberPath="Value" /> </igChart:XamDataChart.Series> </igChart:XamDataChart> </Grid>
The supporting code:
public class ChartBehaviors : DependencyObject { public static readonly DependencyProperty CursorTooltipProperty = DependencyProperty.RegisterAttached("CursorTooltip", typeof(CursorTooltipBehavior), typeof(ChartBehaviors), new PropertyMetadata(null, (o, e) => CursorTooltipChanged( o as XamDataChart, e.OldValue as CursorTooltipBehavior, e.NewValue as CursorTooltipBehavior))); public static CursorTooltipBehavior GetCursorTooltip( DependencyObject target) { return target.GetValue(CursorTooltipProperty) as CursorTooltipBehavior; } public static void SetCursorTooltip( DependencyObject target, CursorTooltipBehavior behavior) { target.SetValue(CursorTooltipProperty, behavior); } private static void CursorTooltipChanged( XamDataChart chart, CursorTooltipBehavior oldValue, CursorTooltipBehavior newValue) { if (chart == null) { return; } if (oldValue != null) { oldValue.OnDetach(chart); } if (newValue != null) { newValue.OnAttach(chart); } } } public class SeriesItemInfo : INotifyPropertyChanged { private Series _series; public Series Series { get { return _series; } set { _series = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("Series")); } } } private object _item; public object Item { get { return _item; } set { _item = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("Item")); } } } public event PropertyChangedEventHandler PropertyChanged; } public class SeriesItemInfoCollection : ObservableCollection<SeriesItemInfo> { public void UpdateSeriesItem( Series series, object item) { bool found = false; var indexes = from curr in this where curr.Series == series select this.IndexOf(curr); foreach (int index in indexes) { found = true; this[index].Item = item; } if (!found) { this.Add( new SeriesItemInfo() { Series = series, Item = item }); } } } public class CursorTooltipBehavior { private bool _isOverChart = false; private Popup _popup = new Popup(); private ContentControl _content = new ContentControl(); private Panel _container; private XamDataChart _owner = null; private DataTemplate _tooltipTemplate; private SeriesItemInfoCollection _items = new SeriesItemInfoCollection(); public DataTemplate TooltipTemplate { get { return _tooltipTemplate; } set { _tooltipTemplate = value; _content.ContentTemplate = _tooltipTemplate; } } protected bool IsOverChart { get { return _isOverChart; } set { bool last = _isOverChart; _isOverChart = value; if (_isOverChart && !last) { ShowPopup(); } if (!_isOverChart && last) { HidePopup(); } } } private void HidePopup() { _popup.IsOpen = false; } private void ShowPopup() { _popup.IsOpen = true; } public void OnAttach(XamDataChart chart) { if (_owner != null) { OnDetach(_owner); } chart.MouseLeave += Chart_MouseLeave; chart.MouseMove += Chart_MouseMove; chart.SeriesCursorMouseMove += Chart_SeriesCursorMouseMove; _popup.IsOpen = false; _popup.Child = _content; _content.ContentTemplate = TooltipTemplate; _content.Content = _items; if (chart.Parent != null && chart.Parent is Panel) { _container = chart.Parent as Panel; _container.Children.Add(_popup); } } public void OnDetach(XamDataChart chart) { if (_owner != chart) { return; } chart.MouseLeave -= Chart_MouseLeave; chart.MouseMove -= Chart_MouseMove; chart.SeriesCursorMouseMove -= Chart_SeriesCursorMouseMove; IsOverChart = false; _items.Clear(); _owner = null; } void Chart_MouseMove(object sender, MouseEventArgs e) { SetPopupOffsets(e.GetPosition(_owner)); IsOverChart = true; } private void SetPopupOffsets(Point point) { _popup.VerticalOffset = point.Y; _popup.HorizontalOffset = point.X + 10; } void Chart_SeriesCursorMouseMove( object sender, ChartCursorEventArgs e) { if (e.Series != null && e.Item != null) { _items.UpdateSeriesItem(e.Series, e.Item); } } void Chart_MouseLeave( object sender, MouseEventArgs e) { IsOverChart = false; } }
Let me know how it goes!
Hello Graham,
Although this is a old post but after spending few hours, I found this post very helpful to me. I have two lines series on my chart with CategoryDateTimeXAxis and NumericYAxis. I need to show both series values in Tooltip with Crosshair.
I used your above code, and I can see tooltip but I am afraid, Item.Value is empty in Tooltip like this.
I used the exact give code in your post. Please help.
The following is a modified version to support storing the values in a single item type:
<UserControl.Resources> <local:TestData x:Key="data" /> <DataTemplate x:Key="tooltipTemplate"> <Border BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Background="White" IsHitTestVisible="False" Padding="5"> <ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <ContentPresenter Content="{Binding}" ContentTemplate="{Binding Series.LegendItemBadgeTemplate}" /> <TextBlock Text="Series: " /> <TextBlock Text="{Binding Series.Title}" /> <TextBlock Text=" " /> <TextBlock Text="Value: " /> <TextBlock Text="{Binding Value}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Border> </DataTemplate> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <igChart:XamDataChart CrosshairVisibility="Visible"> <local:ChartBehaviors.CursorTooltip> <local:CursorTooltipBehavior TooltipTemplate="{StaticResource tooltipTemplate}" /> </local:ChartBehaviors.CursorTooltip> <igChart:XamDataChart.Axes> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{StaticResource data}" Label="{}{Date}" > </igChart:CategoryXAxis> <igChart:NumericYAxis x:Name="yAxis" MinimumValue="0" /> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> <igChart:ColumnSeries x:Name="columnSeries" Title="Column 1" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ItemsSource="{StaticResource data}" ValueMemberPath="Value1" /> <igChart:ColumnSeries x:Name="columnSeries2" Title="Column 2" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ItemsSource="{StaticResource data}" ValueMemberPath="Value2" /> </igChart:XamDataChart.Series> </igChart:XamDataChart> </Grid> </UserControl>
And code behind:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } } public class TestData : ObservableCollection<TestDataItem> { public TestData() { Add(new TestDataItem() { Label = "A", Value1 = 1, Value2 = 4 }); Add(new TestDataItem() { Label = "B", Value1 = 2, Value2 = 3 }); Add(new TestDataItem() { Label = "C", Value1 = 3, Value2 = 2, }); Add(new TestDataItem() { Label = "D", Value1 = 4, Value2 = 1 }); } } public class TestDataItem { public string Label { get; set; } public double Value1 { get; set; } public double Value2 { get; set; } } public class ChartBehaviors : DependencyObject { public static readonly DependencyProperty CursorTooltipProperty = DependencyProperty.RegisterAttached("CursorTooltip", typeof(CursorTooltipBehavior), typeof(ChartBehaviors), new PropertyMetadata(null, (o, e) => CursorTooltipChanged( o as XamDataChart, e.OldValue as CursorTooltipBehavior, e.NewValue as CursorTooltipBehavior))); public static CursorTooltipBehavior GetCursorTooltip( DependencyObject target) { return target.GetValue(CursorTooltipProperty) as CursorTooltipBehavior; } public static void SetCursorTooltip( DependencyObject target, CursorTooltipBehavior behavior) { target.SetValue(CursorTooltipProperty, behavior); } private static void CursorTooltipChanged( XamDataChart chart, CursorTooltipBehavior oldValue, CursorTooltipBehavior newValue) { if (chart == null) { return; } if (oldValue != null) { oldValue.OnDetach(chart); } if (newValue != null) { newValue.OnAttach(chart); } } } public class SeriesItemInfo : INotifyPropertyChanged { private Series _series; public Series Series { get { return _series; } set { _series = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("Series")); } } } private object _item; public object Item { get { return _item; } set { _item = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("Item")); } } } private object _value; public object Value { get { return _value; } set { _value = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("Value")); } } } public event PropertyChangedEventHandler PropertyChanged; } public class SeriesItemInfoCollection : ObservableCollection<SeriesItemInfo> { public void UpdateSeriesItem( Series series, object item) { bool found = false; var indexes = from curr in this where curr.Series == series select this.IndexOf(curr); object value = null; if (item != null && series != null) { var inf = item.GetType() .GetProperty( ((AnchoredCategorySeries)series) .ValueMemberPath); if (inf != null) { value = inf.GetValue(item, null); } } foreach (int index in indexes) { found = true; this[index].Item = item; this[index].Value = value; } if (!found) { this.Add( new SeriesItemInfo() { Series = series, Item = item, Value = value }); } } } public class CursorTooltipBehavior { private bool _isOverChart = false; private Popup _popup = new Popup(); private ContentControl _content = new ContentControl(); private Panel _container; private XamDataChart _owner = null; private DataTemplate _tooltipTemplate; private SeriesItemInfoCollection _items = new SeriesItemInfoCollection(); public DataTemplate TooltipTemplate { get { return _tooltipTemplate; } set { _tooltipTemplate = value; _content.ContentTemplate = _tooltipTemplate; } } protected bool IsOverChart { get { return _isOverChart; } set { bool last = _isOverChart; _isOverChart = value; if (_isOverChart && !last) { ShowPopup(); } if (!_isOverChart && last) { HidePopup(); } } } private void HidePopup() { _popup.IsOpen = false; } private void ShowPopup() { _popup.IsOpen = true; } public void OnAttach(XamDataChart chart) { if (_owner != null) { OnDetach(_owner); } chart.MouseLeave += Chart_MouseLeave; chart.MouseMove += Chart_MouseMove; chart.SeriesCursorMouseMove += Chart_SeriesCursorMouseMove; _popup.IsOpen = false; _popup.Child = _content; _content.ContentTemplate = TooltipTemplate; _content.Content = _items; if (chart.Parent != null && chart.Parent is Panel) { _container = chart.Parent as Panel; _container.Children.Add(_popup); } } public void OnDetach(XamDataChart chart) { if (_owner != chart) { return; } chart.MouseLeave -= Chart_MouseLeave; chart.MouseMove -= Chart_MouseMove; chart.SeriesCursorMouseMove -= Chart_SeriesCursorMouseMove; IsOverChart = false; _items.Clear(); _owner = null; } void Chart_MouseMove(object sender, MouseEventArgs e) { SetPopupOffsets(e.GetPosition(_owner)); IsOverChart = true; } private void SetPopupOffsets(Point point) { _popup.VerticalOffset = point.Y; _popup.HorizontalOffset = point.X + 10; } void Chart_SeriesCursorMouseMove( object sender, ChartCursorEventArgs e) { if (e.Series != null && e.Item != null) { _items.UpdateSeriesItem(e.Series, e.Item); } } void Chart_MouseLeave( object sender, MouseEventArgs e) { IsOverChart = false; } }
There are other ways to accomplish this, but this represents the simplest way, I believe.Hope this helps!-Graham
Hi,
The sample above is structured around there being seperate data items for all the points but with the same property name so that you can use a simple items control and item template in the tooltip. Since you have the data for each series in the same data item you would need to make some modifications in order to be able to display the data from the different series. I'll see if I can modify the sample for your case.
I haven't heard anthing from yourside. Please guide me how can I accomplish my requirements.
Thanks,
M. Irfan
Thank you Graham for your comments.
I have following data item
public class ChartData : INotifyPropertyChanged { private DateTime _date; private double _value1, _value2;
public DateTime Date { get { return _date; } set { if (_date == value) return; _date = value; OnPropertyChanged("Date"); } } public double Value1 { get { return _value1; } set { if (_value1 == value) return; _value1 = value; OnPropertyChanged("Value1"); } } public double Value2 { get { return _value2; } set { if (_value2 == value) return; _value2 = value; OnPropertyChanged("Value2"); } }
Here Value1 is for first line series and Value2 is for second line series. Basically my same data item is being used for both lines series. So how can I get their values?
Hi, what does the class that you are using for your data items look like? Does it have a property called Value? If your value is in a different property make sure you update the binding that reads Item.Value to read Item.YourProperty where YourProperty is whatever property contains the value of the item.
Let me know if this helps.