using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Shapes; using System.Windows.Media; using System.Windows.Input; using Microsoft.Research.DynamicDataDisplay.Common.DataSearch; using System.Diagnostics; using System.Windows.Markup; using System.ComponentModel; using System.Diagnostics.Contracts; namespace Microsoft.Research.DynamicDataDisplay.Charts { /// /// Represents a marker with position.X bound to mouse cursor's position and position.Y is determined by interpolation of 's points. /// [ContentProperty("MarkerTemplate")] public class DataFollowChart : ViewportHostPanel, INotifyPropertyChanged { /// /// Initializes a new instance of the class. /// public DataFollowChart() { Marker = CreateDefaultMarker(); SetX(marker, 0); SetY(marker, 0); Children.Add(marker); } private static Ellipse CreateDefaultMarker() { return new Ellipse { Width = 10, Height = 10, Stroke = Brushes.Green, StrokeThickness = 1, Fill = Brushes.LightGreen, Visibility = Visibility.Hidden }; } /// /// Initializes a new instance of the class, bound to specified . /// /// The point source. public DataFollowChart(PointsGraphBase pointSource) : this() { PointSource = pointSource; } #region MarkerTemplate property /// /// Gets or sets the template, used to create a marker. This is a dependency property. /// /// The marker template. public DataTemplate MarkerTemplate { get { return (DataTemplate)GetValue(MarkerTemplateProperty); } set { SetValue(MarkerTemplateProperty, value); } } /// /// Identifies the dependency property. /// public static readonly DependencyProperty MarkerTemplateProperty = DependencyProperty.Register( "MarkerTemplate", typeof(DataTemplate), typeof(DataFollowChart), new FrameworkPropertyMetadata(null, OnMarkerTemplateChanged)); private static void OnMarkerTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DataFollowChart chart = (DataFollowChart)d; DataTemplate template = (DataTemplate)e.NewValue; FrameworkElement marker; if (template != null) { marker = (FrameworkElement)template.LoadContent(); } else { marker = CreateDefaultMarker(); } chart.Children.Remove(chart.marker); chart.Marker = marker; chart.Children.Add(marker); } #endregion #region Point sources /// /// Gets or sets the source of points. /// Can be null. /// /// The point source. public PointsGraphBase PointSource { get { return (PointsGraphBase)GetValue(PointSourceProperty); } set { SetValue(PointSourceProperty, value); } } /// /// Identifies the dependency property. /// public static readonly DependencyProperty PointSourceProperty = DependencyProperty.Register( "PointSource", typeof(PointsGraphBase), typeof(DataFollowChart), new FrameworkPropertyMetadata(null, OnPointSourceChanged)); private static void OnPointSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DataFollowChart chart = (DataFollowChart)d; PointsGraphBase previous = e.OldValue as PointsGraphBase; if (previous != null) { previous.VisiblePointsChanged -= chart.Source_VisiblePointsChanged; } PointsGraphBase current = e.NewValue as PointsGraphBase; if (current != null) { current.ProvideVisiblePoints = true; current.VisiblePointsChanged += chart.Source_VisiblePointsChanged; if (current.VisiblePoints != null) { chart.searcher = new SortedXSearcher1d(current.VisiblePoints); } } chart.UpdateUIRepresentation(); } private SearchResult1d searchResult = SearchResult1d.Empty; private SortedXSearcher1d searcher; private FrameworkElement marker; [NotNull] public FrameworkElement Marker { get { return marker; } protected set { Contract.Assert(value != null); marker = value; marker.DataContext = followDataContext; PropertyChanged.Raise(this, "Marker"); } } private FollowDataContext followDataContext = new FollowDataContext(); public FollowDataContext FollowDataContext { get { return followDataContext; } } private void UpdateUIRepresentation() { if (Plotter == null) return; PointsGraphBase source = this.PointSource; if (source == null || (source != null && source.VisiblePoints == null)) { SetValue(MarkerPositionPropertyKey, new Point(Double.NaN, Double.NaN)); marker.Visibility = Visibility.Hidden; return; } else { Point mousePos = Mouse.GetPosition(Plotter.CentralGrid); var transform = Plotter.Transform; Point viewportPos = mousePos.ScreenToViewport(transform); double x = viewportPos.X; searchResult = searcher.SearchXBetween(x, searchResult); SetValue(ClosestPointIndexPropertyKey, searchResult.Index); if (!searchResult.IsEmpty) { marker.Visibility = Visibility.Visible; IList points = source.VisiblePoints; Point ptBefore = points[searchResult.Index]; Point ptAfter = points[searchResult.Index + 1]; double ratio = (x - ptBefore.X) / (ptAfter.X - ptBefore.X); double y = ptBefore.Y + (ptAfter.Y - ptBefore.Y) * ratio; Point temp = new Point(x, y); SetX(marker, temp.X); SetY(marker, temp.Y); Point markerPosition = temp; followDataContext.Position = markerPosition; SetValue(MarkerPositionPropertyKey, markerPosition); } else { SetValue(MarkerPositionPropertyKey, new Point(Double.NaN, Double.NaN)); marker.Visibility = Visibility.Hidden; } } } #region ClosestPointIndex property private static readonly DependencyPropertyKey ClosestPointIndexPropertyKey = DependencyProperty.RegisterReadOnly( "ClosestPointIndex", typeof(int), typeof(DataFollowChart), new PropertyMetadata(-1) ); public static readonly DependencyProperty ClosestPointIndexProperty = ClosestPointIndexPropertyKey.DependencyProperty; public int ClosestPointIndex { get { return (int)GetValue(ClosestPointIndexProperty); } } #endregion #region MarkerPositionProperty private static readonly DependencyPropertyKey MarkerPositionPropertyKey = DependencyProperty.RegisterReadOnly( "MarkerPosition", typeof(Point), typeof(DataFollowChart), new PropertyMetadata(new Point(), OnMarkerPositionChanged)); private static void OnMarkerPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DataFollowChart chart = (DataFollowChart)d; chart.MarkerPositionChanged.Raise(chart); } /// /// Identifies the dependency property. /// public static readonly DependencyProperty MarkerPositionProperty = MarkerPositionPropertyKey.DependencyProperty; /// /// Gets the marker position. /// /// The marker position. public Point MarkerPosition { get { return (Point)GetValue(MarkerPositionProperty); } } /// /// Occurs when marker position changes. /// public event EventHandler MarkerPositionChanged; #endregion public override void OnPlotterAttached(Plotter plotter) { base.OnPlotterAttached(plotter); plotter.MainGrid.MouseMove += MainGrid_MouseMove; } private void MainGrid_MouseMove(object sender, MouseEventArgs e) { UpdateUIRepresentation(); } public override void OnPlotterDetaching(Plotter plotter) { plotter.MainGrid.MouseMove -= MainGrid_MouseMove; base.OnPlotterDetaching(plotter); } protected override void Viewport_PropertyChanged(object sender, ExtendedPropertyChangedEventArgs e) { base.Viewport_PropertyChanged(sender, e); UpdateUIRepresentation(); } private void Source_VisiblePointsChanged(object sender, EventArgs e) { PointsGraphBase source = (PointsGraphBase)sender; if (source.VisiblePoints != null) { searcher = new SortedXSearcher1d(source.VisiblePoints); } UpdateUIRepresentation(); } #endregion #region INotifyPropertyChanged Members /// /// Occurs when a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; #endregion } /// /// Represents a special data context, which encapsulates marker's position and custom data. /// Used in . /// public class FollowDataContext : INotifyPropertyChanged { private Point position; /// /// Gets or sets the position of marker. /// /// The position. public Point Position { get { return position; } set { position = value; PropertyChanged.Raise(this, "Position"); } } private object data; /// /// Gets or sets the additional custom data. /// /// The data. public object Data { get { return data; } set { data = value; PropertyChanged.Raise(this, "Data"); } } #region INotifyPropertyChanged Members /// /// Occurs when a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; #endregion } }