using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Diagnostics; using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary; using Microsoft.Research.DynamicDataDisplay; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using Microsoft.Research.DynamicDataDisplay.Common; namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation { /// /// Adds to ChartPlotter two crossed lines, bound to mouse cursor position, and two labels near axes with mouse position in its text. /// public partial class CursorCoordinateGraph : ContentGraph { /// /// Initializes a new instance of the class. /// public CursorCoordinateGraph() { InitializeComponent(); } Vector blockShift = new Vector(3, 3); #region Plotter protected override void OnPlotterAttached() { UIElement parent = (UIElement)Parent; parent.MouseMove += parent_MouseMove; parent.MouseEnter += Parent_MouseEnter; parent.MouseLeave += Parent_MouseLeave; UpdateVisibility(); UpdateUIRepresentation(); } protected override void OnPlotterDetaching() { UIElement parent = (UIElement)Parent; parent.MouseMove -= parent_MouseMove; parent.MouseEnter -= Parent_MouseEnter; parent.MouseLeave -= Parent_MouseLeave; } #endregion private bool autoHide = true; /// /// Gets or sets a value indicating whether to hide automatically cursor lines when mouse leaves plotter. /// /// true if auto hide; otherwise, false. public bool AutoHide { get { return autoHide; } set { autoHide = value; } } private void Parent_MouseEnter(object sender, MouseEventArgs e) { if (autoHide) { UpdateVisibility(); } } private void UpdateVisibility() { horizLine.Visibility = vertGrid.Visibility = GetHorizontalVisibility(); vertLine.Visibility = horizGrid.Visibility = GetVerticalVisibility(); } private Visibility GetHorizontalVisibility() { return showHorizontalLine ? Visibility.Visible : Visibility.Hidden; } private Visibility GetVerticalVisibility() { return showVerticalLine ? Visibility.Visible : Visibility.Hidden; } private void Parent_MouseLeave(object sender, MouseEventArgs e) { if (autoHide) { horizLine.Visibility = Visibility.Hidden; vertLine.Visibility = Visibility.Hidden; horizGrid.Visibility = Visibility.Hidden; vertGrid.Visibility = Visibility.Hidden; } } private bool followMouse = true; /// /// Gets or sets a value indicating whether lines are following mouse cursor position. /// /// true if lines are following mouse cursor position; otherwise, false. public bool FollowMouse { get { return followMouse; } set { followMouse = value; if (!followMouse) { AutoHide = false; } UpdateUIRepresentation(); } } private void parent_MouseMove(object sender, MouseEventArgs e) { if (followMouse) { UpdateUIRepresentation(); } } protected override void OnViewportPropertyChanged(ExtendedPropertyChangedEventArgs e) { UpdateUIRepresentation(); } private string customXFormat = null; /// /// Gets or sets the custom format string of x label. /// /// The custom X format. public string CustomXFormat { get { return customXFormat; } set { if (customXFormat != value) { customXFormat = value; UpdateUIRepresentation(); } } } private string customYFormat = null; /// /// Gets or sets the custom format string of y label. /// /// The custom Y format. public string CustomYFormat { get { return customYFormat; } set { if (customYFormat != value) { customYFormat = value; UpdateUIRepresentation(); } } } private Func xTextMapping = null; /// /// Gets or sets the text mapping of x label - function that builds text from x-coordinate of mouse in data. /// /// The X text mapping. public Func XTextMapping { get { return xTextMapping; } set { if (xTextMapping != value) { xTextMapping = value; UpdateUIRepresentation(); } } } private Func yTextMapping = null; /// /// Gets or sets the text mapping of y label - function that builds text from y-coordinate of mouse in data. /// /// The Y text mapping. public Func YTextMapping { get { return yTextMapping; } set { if (yTextMapping != value) { yTextMapping = value; UpdateUIRepresentation(); } } } private bool showHorizontalLine = true; /// /// Gets or sets a value indicating whether to show horizontal line. /// /// true if horizontal line is shown; otherwise, false. public bool ShowHorizontalLine { get { return showHorizontalLine; } set { if (showHorizontalLine != value) { showHorizontalLine = value; UpdateVisibility(); } } } private bool showVerticalLine = true; /// /// Gets or sets a value indicating whether to show vertical line. /// /// true if vertical line is shown; otherwise, false. public bool ShowVerticalLine { get { return showVerticalLine; } set { if (showVerticalLine != value) { showVerticalLine = value; UpdateVisibility(); } } } private void UpdateUIRepresentation() { Point position = followMouse ? Mouse.GetPosition(this) : Position; UpdateUIRepresentation(position); } private void UpdateUIRepresentation(Point mousePos) { if (Plotter2D == null) return; var transform = Plotter2D.Viewport.Transform; DataRect visible = Plotter2D.Viewport.Visible; Rect output = Plotter2D.Viewport.Output; if (!output.Contains(mousePos)) { if (autoHide) { horizGrid.Visibility = horizLine.Visibility = vertGrid.Visibility = vertLine.Visibility = Visibility.Hidden; } return; } if (!followMouse) { mousePos = mousePos.DataToScreen(transform); } horizLine.X1 = output.Left; horizLine.X2 = output.Right; horizLine.Y1 = mousePos.Y; horizLine.Y2 = mousePos.Y; vertLine.X1 = mousePos.X; vertLine.X2 = mousePos.X; vertLine.Y1 = output.Top; vertLine.Y2 = output.Bottom; if (UseDashOffset) { horizLine.StrokeDashOffset = (output.Right - mousePos.X) / 2; vertLine.StrokeDashOffset = (output.Bottom - mousePos.Y) / 2; } Point mousePosInData = mousePos.ScreenToData(transform); string text = null; if (showVerticalLine) { double xValue = mousePosInData.X; if (xTextMapping != null) text = xTextMapping(xValue); // doesnot have xTextMapping or it returned null if (text == null) text = GetRoundedValue(visible.XMin, visible.XMax, xValue); if (!String.IsNullOrEmpty(customXFormat)) text = String.Format(customXFormat, text); horizTextBlock.Text = text; } double width = horizGrid.ActualWidth; double x = mousePos.X + blockShift.X; if (x + width > output.Right) { x = mousePos.X - blockShift.X - width; } Canvas.SetLeft(horizGrid, x); if (showHorizontalLine) { double yValue = mousePosInData.Y; text = null; if (yTextMapping != null) text = yTextMapping(yValue); if (text == null) text = GetRoundedValue(visible.YMin, visible.YMax, yValue); if (!String.IsNullOrEmpty(customYFormat)) text = String.Format(customYFormat, text); vertTextBlock.Text = text; } // by default vertGrid is positioned on the top of line. double height = vertGrid.ActualHeight; double y = mousePos.Y - blockShift.Y - height; if (y < output.Top) { y = mousePos.Y + blockShift.Y; } Canvas.SetTop(vertGrid, y); if (followMouse) Position = mousePos; } /// /// Gets or sets the mouse position in screen coordinates. /// /// The position. public Point Position { get { return (Point)GetValue(PositionProperty); } set { SetValue(PositionProperty, value); } } /// /// Identifies Position dependency property. /// public static readonly DependencyProperty PositionProperty = DependencyProperty.Register( "Position", typeof(Point), typeof(CursorCoordinateGraph), new UIPropertyMetadata(new Point(), OnPositionChanged)); private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { CursorCoordinateGraph graph = (CursorCoordinateGraph)d; graph.UpdateUIRepresentation((Point)e.NewValue); } private string GetRoundedValue(double min, double max, double value) { double roundedValue = value; var log = RoundingHelper.GetDifferenceLog(min, max); string format = "G3"; double diff = Math.Abs(max - min); if (1E3 < diff && diff < 1E6) { format = "F0"; } if (log < 0) format = "G" + (-log + 2).ToString(); return roundedValue.ToString(format); } #region UseDashOffset property public bool UseDashOffset { get { return (bool)GetValue(UseDashOffsetProperty); } set { SetValue(UseDashOffsetProperty, value); } } public static readonly DependencyProperty UseDashOffsetProperty = DependencyProperty.Register( "UseDashOffset", typeof(bool), typeof(CursorCoordinateGraph), new FrameworkPropertyMetadata(true, UpdateUIRepresentation)); private static void UpdateUIRepresentation(DependencyObject d, DependencyPropertyChangedEventArgs e) { CursorCoordinateGraph graph = (CursorCoordinateGraph)d; if ((bool)e.NewValue) { graph.UpdateUIRepresentation(); } else { graph.vertLine.ClearValue(Line.StrokeDashOffsetProperty); graph.horizLine.ClearValue(Line.StrokeDashOffsetProperty); } } #endregion #region LineStroke property public Brush LineStroke { get { return (Brush)GetValue(LineStrokeProperty); } set { SetValue(LineStrokeProperty, value); } } public static readonly DependencyProperty LineStrokeProperty = DependencyProperty.Register( "LineStroke", typeof(Brush), typeof(CursorCoordinateGraph), new PropertyMetadata(new SolidColorBrush(Color.FromArgb(170, 86, 86, 86)))); #endregion #region LineStrokeThickness property public double LineStrokeThickness { get { return (double)GetValue(LineStrokeThicknessProperty); } set { SetValue(LineStrokeThicknessProperty, value); } } public static readonly DependencyProperty LineStrokeThicknessProperty = DependencyProperty.Register( "LineStrokeThickness", typeof(double), typeof(CursorCoordinateGraph), new PropertyMetadata(2.0)); #endregion #region LineStrokeDashArray property [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public DoubleCollection LineStrokeDashArray { get { return (DoubleCollection)GetValue(LineStrokeDashArrayProperty); } set { SetValue(LineStrokeDashArrayProperty, value); } } public static readonly DependencyProperty LineStrokeDashArrayProperty = DependencyProperty.Register( "LineStrokeDashArray", typeof(DoubleCollection), typeof(CursorCoordinateGraph), new FrameworkPropertyMetadata(DoubleCollectionHelper.Create(3, 3))); #endregion } }