I want to draw a line on the background plot area of the chart. I need to set the line start and end point based on X and Y values coming from a scatter line series. I know I can convert the mouse position to series values, but I need to go the other way around. Is this possible?
Hello,
Thank you for your post. I have been looking into your question and I can suggest you look into the following link from our online documentation which describes how to create a Custom Axis Tickmark Values :
http://help.infragistics.com/NetAdvantage/WPF/2013.1/CLR4.0/?page=xamDataChart_Creating_Custom_Axis_Tickmark_Values.html
Also you can read the following forum thread where Graham Murray has gave suggestions how to draw a line on the XamDataChart control :
http://es.infragistics.com/community/forums/t/48149.aspx
Let me know, if you need any further assistance on this matter.
Sorry should have explained a little more. I need to draw a sloped line. The start and end points will be somewhere in the chart. The user will then need to be able to move the start or end marker in any direction with the line redrawing.
I went a different direction on this issue. Thanks for the help.
I am just checking if you require any further assistance on the matter.
There is something messed up with the forums, you may need to view source to grab the above
Here is an example that you should find useful:
<Window.Resources> <local:TestData x:Key="data" /> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <igChart:XamDataChart x:Name="theChart" VerticalZoomable="True" HorizontalZoomable="True"> <igChart:XamDataChart.Axes> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{StaticResource data}" > <igChart:CategoryXAxis.LabelSettings> <igChart:AxisLabelSettings Visibility="Collapsed" /> </igChart:CategoryXAxis.LabelSettings> </igChart:CategoryXAxis> <igChart:NumericYAxis x:Name="yAxis" /> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> <igChart:FinancialPriceSeries x:Name="priceSeries" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ItemsSource="{StaticResource data}" OpenMemberPath="Open" CloseMemberPath="Close" HighMemberPath="High" LowMemberPath="Low" DisplayType="Candlestick"/> </igChart:XamDataChart.Series> </igChart:XamDataChart> <local:FibonacciOverlay x:Name="overlay" Chart="{Binding ElementName=theChart}" /> <Button Grid.Row="1" x:Name="startAnnotation" Click="startAnnotation_Click" Content="Start Annotation" /> </Grid>
And code behind:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void startAnnotation_Click(object sender, RoutedEventArgs e) { overlay.ShouldCatchInput = true; } } public class TestData : ObservableCollection<TestDataItem> { private Random _rand = new Random(); public TestData() { var curr = 10.0; var currHigh = 10.0; var currLow = 10.0; var currOpen = 10.0; var currClose = 10.0; for (var i = 0; i < 10000; i++) { if (_rand.NextDouble() > .5) { curr += _rand.NextDouble() * 2.0; } else { curr -= _rand.NextDouble() * 2.0; } if (_rand.NextDouble() > .5) { currOpen = curr + _rand.NextDouble() * 2.0; currClose = curr - _rand.NextDouble() * 2.0; currHigh = currOpen + _rand.NextDouble() * 2.0; currLow = currClose - _rand.NextDouble() * 2.0; } else { currOpen -= _rand.NextDouble() * 2.0; currClose += _rand.NextDouble() * 2.0; currHigh = currClose + _rand.NextDouble() * 2.0; currLow = currOpen - _rand.NextDouble() * 2.0; } Add(new TestDataItem() { Open= currOpen, High=currHigh, Close=currClose, Low= currLow }); } } } public class TestDataItem { public double Open { get; set; } public double High { get; set; } public double Low { get; set; } public double Close { get; set; } } public class ChartOverlay : ContentControl { protected Canvas _overlayCanvas = new Canvas(); public ChartOverlay() { Viewport = Rect.Empty; HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch; VerticalContentAlignment = System.Windows.VerticalAlignment.Stretch; Content = _overlayCanvas; } public XamDataChart Chart { get { return (XamDataChart)GetValue(ChartProperty); } set { SetValue(ChartProperty, value); } } public static readonly DependencyProperty ChartProperty = DependencyProperty.Register( "Chart", typeof(XamDataChart), typeof(ChartOverlay), new PropertyMetadata( null, (o, e) => (o as ChartOverlay).OnChartChanged( (XamDataChart)e.OldValue, (XamDataChart)e.NewValue))); private void OnChartChanged(XamDataChart oldChart, XamDataChart newChart) { if (oldChart != null) { Detach(oldChart); } if (newChart != null) { Attach(newChart); } } protected XamDataChart _chart = null; private void Attach(XamDataChart newChart) { _chart = newChart; newChart.RefreshCompleted += RefreshCompleted; } protected Rect Viewport { get; set; } private void RefreshCompleted(object sender, EventArgs e) { Viewport = GetChartViewport(); DoRefresh(); } private Rect GetChartViewport() { if (_chart == null) { return Rect.Empty; } var eles = _chart.Axes.OfType<FrameworkElement>() .Concat(_chart.Series.OfType<FrameworkElement>()); if (!eles.Any()) { return Rect.Empty; } var first = eles.First(); Point topLeft; try { topLeft = first.TransformToVisual(this).Transform(new Point(0, 0)); } catch (Exception e) { return Rect.Empty; } return new Rect( topLeft.X, topLeft.Y, _chart.ViewportRect.Width, _chart.ViewportRect.Height); } protected void DoRefresh() { DoRefreshOverride(); } protected virtual void DoRefreshOverride() { } private void Detach(XamDataChart oldChart) { oldChart.RefreshCompleted -= RefreshCompleted; } public bool ShouldCatchInput { get { return (bool)GetValue(ShouldCatchInputProperty); } set { SetValue(ShouldCatchInputProperty, value); } } public static readonly DependencyProperty ShouldCatchInputProperty = DependencyProperty.Register( "ShouldCatchInput", typeof(bool), typeof(ChartOverlay), new PropertyMetadata(false, (o, e) => (o as ChartOverlay) .OnShouldCatchInputChanged( (bool)e.OldValue, (bool)e.NewValue))); private void OnShouldCatchInputChanged( bool oldValue, bool newValue) { if (newValue) { _overlayCanvas.Background = new SolidColorBrush(Color.FromArgb(20, 0, 0, 0)); } else { _overlayCanvas.Background = null; } } } public class FibonacciOverlay : ChartOverlay { public FibonacciOverlay() { this.MouseDown += FibonacciOverlay_MouseDown; this.MouseUp += FibonacciOverlay_MouseUp; this.MouseMove += FibonacciOverlay_MouseMove; _fibCanvas = new Canvas(); _fibCanvas.IsHitTestVisible = false; } private bool _isDragging = false; private bool _isPlaced = false; private Canvas _fibCanvas; protected CategoryXAxis XAxis { get { return _chart.Axes.OfType<CategoryXAxis>().First(); } } protected NumericYAxis YAxis { get { return _chart.Axes.OfType<NumericYAxis>().First(); } } void FibonacciOverlay_MouseMove(object sender, MouseEventArgs e) { if (_isDragging) { var pos = e.GetPosition(XAxis); System.Diagnostics.Debug.WriteLine(pos); var scaled = GetUnscaled(pos); var pointX = Math.Min(_fibAnchor.X, scaled.X); var pointY = Math.Max(_fibAnchor.Y, scaled.Y); _fibWidth = Math.Abs(_fibAnchor.X - scaled.X); _fibHeight = Math.Abs(_fibAnchor.Y - scaled.Y); _fibPoint = new Point(pointX, pointY); DoRefresh(); e.Handled = true; } } void FibonacciOverlay_MouseUp(object sender, MouseButtonEventArgs e) { if (_isDragging) { _isDragging = false; ShouldCatchInput = false; DoRefresh(); ReleaseMouseCapture(); } } private Point _fibPoint { get; set; } private Point _fibAnchor { get; set; } private double _fibWidth { get; set; } private double _fibHeight { get; set; } void FibonacciOverlay_MouseDown(object sender, MouseButtonEventArgs e) { if (_chart == null) { return; } e.Handled = true; if (_isPlaced) { _overlayCanvas.Children.Remove(_fibCanvas); } if (this.CaptureMouse()) { _isDragging = true; _overlayCanvas.Children.Add(_fibCanvas); _isPlaced = true; Point pos = e.GetPosition(XAxis); _fibAnchor = GetUnscaled(pos); _fibPoint = GetUnscaled(pos); DoRefresh(); } } private double GetUnscaledX(double x) { return XAxis.UnscaleValue(x); } private double GetUnscaledY(double y) { return YAxis.UnscaleValue(y); } private Point GetUnscaled(Point pos) { return new Point( GetUnscaledX(pos.X), GetUnscaledY(pos.Y)); } protected override void DoRefreshOverride() { base.DoRefreshOverride(); if (Viewport.IsEmpty) { return; } var x = XAxis.ScaleValue(_fibPoint.X); var y = YAxis.ScaleValue(_fibPoint.Y); var right = XAxis.ScaleValue(_fibPoint.X + _fibWidth); var bottom = YAxis.ScaleValue(_fibPoint.Y - _fibHeight); Canvas.SetLeft(_fibCanvas, Viewport.Left + x); Canvas.SetTop(_fibCanvas, Viewport.Top + y); var viewport = new Rect(0, 0, right - x, bottom - y); SetClipRectangle(); RefreshFibVisual(viewport); } private void SetClipRectangle() { _overlayCanvas.Clip = new RectangleGeometry() { Rect = Viewport }; } private List<FibEntry> Entries { get; set; } private void RefreshFibVisual(Rect viewport) { if (_fibCanvas.Children.Count == 0) { List<double> values = new List<double>() { 0.0, 23.6, 38.2, 100.0 }; Entries = new List<FibEntry>(); foreach (var val in values) { Entries.Add( new FibEntry() { Line = new Line() { Stroke = new SolidColorBrush(Colors.Red), StrokeThickness = 1 }, TextBlock = new TextBlock() { Text = val.ToString(), Foreground = new SolidColorBrush(Colors.Red), }, Value = val }); } foreach (var entry in Entries) { _fibCanvas.Children.Add(entry.Line); _fibCanvas.Children.Add(entry.TextBlock); entry.TextBlock.InvalidateMeasure(); entry.TextBlock.Measure( new Size(double.PositiveInfinity, double.PositiveInfinity)); } } if (viewport.Width == 0 || viewport.Height == 0 || double.IsNaN(viewport.Width) || double.IsNaN(viewport.Height)) { return; } foreach (var entry in Entries) { var yPos = viewport.Bottom - (viewport.Height * entry.Value / 100.0); var xLeft = viewport.Left; var xRight = viewport.Right; if (double.IsNaN(yPos) || double.IsNaN(xLeft) || double.IsNaN(xRight)) { continue; } entry.Line.X1 = xLeft; entry.Line.X2 = xRight; entry.Line.Y1 = yPos; entry.Line.Y2 = yPos; Canvas.SetTop(entry.TextBlock, yPos - entry.TextBlock.DesiredSize.Height); Canvas.SetLeft(entry.TextBlock, xRight - entry.TextBlock.DesiredSize.Width); } } } public class FibEntry { public Line Line { get; set; } public TextBlock TextBlock { get; set; } public double Value { get; set; } }
Hi, if you call UnscaleValue on the axes it will return from a pixel coordinate in the series to an axis value on that axis. Does that help?