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
}
}