From 0fda2ba3ff49bdc1ffc6833f658e2164af187008 Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Tue, 16 Jan 2018 12:17:10 +0200 Subject: Embedded RealTimeGraphEx library to solution. Added graphs to technician view. Implemented simple sensors data test using Machine Emulator. --- .../RealTimeGraphEx/RealTimeGraphExBase.cs | 1346 ++++++++++++++++++++ 1 file changed, 1346 insertions(+) create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs (limited to 'Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs') diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs b/Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs new file mode 100644 index 000000000..cf8d6c0fe --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs @@ -0,0 +1,1346 @@ +using RealTimeGraphEx.Components; +using RealTimeGraphEx.Enums; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using System.Windows.Threading; + +namespace RealTimeGraphEx +{ + #region Delegates + + public delegate void ZoomCompleteDelegate(Point transformOrigin, double scaleX, double scaleY); + public delegate void PanningCompleteDelegate(Point translate); + + #endregion + + /// + /// Represents an abstract base class for the RealTimeGraphEx. + /// + [ContentProperty("Components")] + public abstract class RealTimeGraphExBase : UserControl, IDisposable + { + private Rect lastBounds; + private Point lastCenter; + private bool _loaded; + + #region Events + + /// + /// Occurs just before rendering the actual visuals on the writeable bitmap. + /// + public event Action BeforeRenderVisuals; + + /// + /// Occurs after rendering the actual visuals on the writeable bitmap. + /// + public event Action AfterRenderVisuals; + + /// + /// Occurs when [zooming to]. + /// + public event ZoomCompleteDelegate ZoomComplete; + + /// + /// Occurs when [panning to]. + /// + public event PanningCompleteDelegate PanningComplete; + + /// + /// Occurs when the mouse pointer moves while over this element. + /// + public new event MouseEventHandler MouseMove; + + /// + /// Occurs when the mouse pointer leaves the bounds of this element. + /// + public new event MouseEventHandler MouseLeave; + + #endregion + + #region Protected Fields + + //Visuals + internal Grid gridMain; + internal Grid gridLinesAndImageWrapperGrid; + internal Grid gridBack; + internal StackPanel stackLeft; + internal StackPanel stackRight; + internal double virtualMinimum; + internal double virtualMaximum; + internal double virtualStart; + internal double virtualEnd; + protected Image img; + internal Grid gridInnerContentWrapper; + //protected ScaleTransform scaleTransform; + //protected TranslateTransform translateTransform; + protected Canvas selectionCanvas; + protected Rectangle selectionRectangle; + protected Thumb moveThumb; + protected bool isSelectionMouseDown; + protected bool isScaled; + + //Multi threading + protected Thread pushThread; + protected ManualResetEvent _requestTermination; + protected ManualResetEvent _terminated; + + //Plotting + protected double xValueCounter; + + //Resizing + protected DispatcherTimer resizeTimer; + protected bool isAfterResize; + + #endregion + + #region Properties + + /// + /// Gets or sets whether this graph is controlled by a synchronization manager. + /// + internal bool IsSynced { get; set; } + + /// + /// Gets or sets an extra content which will be placed on top of the graph control. + /// + public FrameworkElement InnerContent + { + get { return (FrameworkElement)GetValue(InnerContentProperty); } + set { SetValue(InnerContentProperty, value); } + } + public static readonly DependencyProperty InnerContentProperty = + DependencyProperty.Register("InnerContent", typeof(FrameworkElement), typeof(RealTimeGraphExBase), new PropertyMetadata(null, new PropertyChangedCallback(InnerContentChanged))); + private static void InnerContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + //Add the content control to the inner wrapper grid. + (d as RealTimeGraphExBase).gridInnerContentWrapper.Children.Add((d as RealTimeGraphExBase).InnerContent); + } + + /// + /// Gets or sets the maximum expected value to be plotted on the graph (default 255). + /// + public double Maximum + { + get { return (double)GetValue(MaximumProperty); } + set { SetValue(MaximumProperty, value); } + } + public static readonly DependencyProperty MaximumProperty = + DependencyProperty.Register("Maximum", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(255.0, new PropertyChangedCallback(CrossModelChanged))); + + /// + /// Gets or sets the minimum expected value to be plotted on the graph (default 0). + /// + public double Minimum + { + get { return (double)GetValue(MinimumProperty); } + set { SetValue(MinimumProperty, value); } + } + public static readonly DependencyProperty MinimumProperty = + DependencyProperty.Register("Minimum", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(0.0, new PropertyChangedCallback(CrossModelChanged))); + + /// + /// Gets or sets the graph refresh rate in milliseconds (default 30, affects performance). + /// + public int RefreshRate + { + get { return (int)GetValue(RefreshRateProperty); } + set { SetValue(RefreshRateProperty, value); } + } + public static readonly DependencyProperty RefreshRateProperty = + DependencyProperty.Register("RefreshRate", typeof(int), typeof(RealTimeGraphExBase), new PropertyMetadata(30, new PropertyChangedCallback(CrossModelChanged))); + + /// + /// Gets or sets whether the graph will be rendered antialiased (affects performance). + /// + public bool Antialiased + { + get { return (bool)GetValue(AntialiasedProperty); } + set { SetValue(AntialiasedProperty, value); } + } + public static readonly DependencyProperty AntialiasedProperty = + DependencyProperty.Register("Antialiased", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(true, new PropertyChangedCallback(CrossModelChanged))); + + /// + /// Gets or sets the collection of graph add-on components. + /// + public ObservableCollection Components + { + get { return (ObservableCollection)GetValue(ComponentsProperty); } + set { SetValue(ComponentsProperty, value); } + } + public static readonly DependencyProperty ComponentsProperty = + DependencyProperty.Register("Components", typeof(ObservableCollection), typeof(RealTimeGraphExBase), new PropertyMetadata(null, new PropertyChangedCallback(ComponentsChanged))); + private static void ComponentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + (d as RealTimeGraphExBase).OnRenderComponents(); + } + + /// + /// Gets or sets a value indicating whether this graph is paused. + /// + /// + /// true if this instance is paused; otherwise, false. + /// + public bool IsPaused + { + get { return (bool)GetValue(IsPausedProperty); } + set { SetValue(IsPausedProperty, value); } + } + public static readonly DependencyProperty IsPausedProperty = + DependencyProperty.Register("IsPaused", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(false, new PropertyChangedCallback(CrossModelChanged))); + + ///// + ///// Gets or sets the zoom level for the graph. + ///// + public double Zoom + { + get { return (double)GetValue(ZoomProperty); } + set { SetValue(ZoomProperty, value); } + } + public static readonly DependencyProperty ZoomProperty = + DependencyProperty.Register("Zoom", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(1.0, new PropertyChangedCallback(ZoomChanged))); + private static void ZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = d as RealTimeGraphExBase; + + if (control.ZoomMode != ZoomModeEnum.Manual && control.ZoomMode != ZoomModeEnum.MouseWheel) return; + + if (control.Zoom < 1) + { + control.Zoom = 1; + return; + } + + if (control.Zoom > 1) + { + control.gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll; + } + else + { + control.gridLinesAndImageWrapperGrid.Cursor = Cursors.Arrow; + } + + control.gridLinesAndImageWrapperGrid.RenderTransformOrigin = new Point(0.5, 0.5); + //control.scaleTransform.ScaleX = control.Zoom; + //control.scaleTransform.ScaleY = control.Zoom; + + double desiredWidth = control.gridMain.ActualWidth * control.Zoom; + double desiredHeight = control.gridMain.ActualHeight * control.Zoom; + double desiredLeft = ((desiredWidth - control.gridMain.ActualWidth) / 2) * -1; + double desiredTop = ((desiredHeight - control.gridMain.ActualHeight) / 2) * -1; + + control.gridLinesAndImageWrapperGrid.Width = desiredWidth; + control.gridLinesAndImageWrapperGrid.Height = desiredHeight; + control.gridLinesAndImageWrapperGrid.Margin = new Thickness(desiredLeft, desiredTop, 0, 0); + + Point relativePoint = control.gridLinesAndImageWrapperGrid.TransformToAncestor(control.gridMain).Transform(new Point(0, 0)); + Rect bounds = control.BoundsRelativeTo(control.gridLinesAndImageWrapperGrid, control.gridMain); + + if (desiredLeft > 0) + { + control.gridLinesAndImageWrapperGrid.Margin = new Thickness(0, control.gridLinesAndImageWrapperGrid.Margin.Top, 0, 0); + } + + if (desiredTop > 0) + { + control.gridLinesAndImageWrapperGrid.Margin = new Thickness(control.gridLinesAndImageWrapperGrid.Margin.Left, 0, 0, 0); + } + + if (desiredWidth < control.gridMain.ActualWidth) + { + control.gridLinesAndImageWrapperGrid.Margin = new Thickness(0, control.gridLinesAndImageWrapperGrid.Margin.Top, 0, 0); + } + + if (desiredHeight < control.gridMain.ActualHeight) + { + control.gridLinesAndImageWrapperGrid.Margin = new Thickness(control.gridLinesAndImageWrapperGrid.Margin.Left, 0, 0, 0); + } + + control.OnZoomingComplete(new Point(0, 0), control.Zoom, control.Zoom); + } + + /// + /// Gets or sets the zoom mode. + /// + /// + /// The zoom mode. + /// + public ZoomModeEnum ZoomMode + { + get { return (ZoomModeEnum)GetValue(ZoomModeProperty); } + set { SetValue(ZoomModeProperty, value); } + } + public static readonly DependencyProperty ZoomModeProperty = + DependencyProperty.Register("ZoomMode", typeof(ZoomModeEnum), typeof(RealTimeGraphExBase), new PropertyMetadata(ZoomModeEnum.Selection)); + + /// + /// Gets or sets the mouse value. + /// + /// + /// The mouse value. + /// + public double MouseValue + { + get { return (double)GetValue(MouseValueProperty); } + set { SetValue(MouseValueProperty, value); } + } + public static readonly DependencyProperty MouseValueProperty = + DependencyProperty.Register("MouseValue", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(0.0)); + + /// + /// Gets or sets the selection fill. + /// + /// + /// The selection fill. + /// + public Brush SelectionFill + { + get { return (Brush)GetValue(SelectionFillProperty); } + set { SetValue(SelectionFillProperty, value); } + } + public static readonly DependencyProperty SelectionFillProperty = + DependencyProperty.Register("SelectionFill", typeof(Brush), typeof(RealTimeGraphExBase), new PropertyMetadata(new SolidColorBrush() { Color = Colors.Black, Opacity = 0.2 }, new PropertyChangedCallback(SelectionFillChanged))); + private static void SelectionFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = d as RealTimeGraphExBase; + control.selectionRectangle.Fill = control.SelectionFill; + } + + /// + /// Gets or sets the selection fill. + /// + /// + /// The selection fill. + /// + public Brush SelectionStroke + { + get { return (Brush)GetValue(SelectionStrokeProperty); } + set { SetValue(SelectionStrokeProperty, value); } + } + public static readonly DependencyProperty SelectionStrokeProperty = + DependencyProperty.Register("SelectionStroke", typeof(Brush), typeof(RealTimeGraphExBase), new PropertyMetadata(Brushes.White, new PropertyChangedCallback(SelectionStrokeChanged))); + private static void SelectionStrokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = d as RealTimeGraphExBase; + control.selectionRectangle.Stroke = control.SelectionStroke; + } + + public ZoomDirectionEnum ZoomDirection + { + get { return (ZoomDirectionEnum)GetValue(ZoomDirectionProperty); } + set { SetValue(ZoomDirectionProperty, value); } + } + public static readonly DependencyProperty ZoomDirectionProperty = + DependencyProperty.Register("ZoomDirection", typeof(ZoomDirectionEnum), typeof(RealTimeGraphExBase), new PropertyMetadata(ZoomDirectionEnum.Both)); + + #endregion + + #region Cross Thread Fields + + protected double _height; + protected double _width; + protected double _mainHeight; + protected double _mainWidth; + protected double _offSetX; + protected double _offSetY; + protected int _refreshRate; + protected double _maximum; + protected double _minimum; + protected double _originalMinimum; + protected bool _antialiased; + protected bool _isPaused; + protected bool _disableRendering; + #endregion + + #region Constructors + + /// + /// Initializes a new instance of RealTimeGraphExBase + /// + public RealTimeGraphExBase() + { + this.Loaded += OnGraphLoaded; + this.SizeChanged += OnSizeChanged; + resizeTimer = new DispatcherTimer(); + resizeTimer.Interval = TimeSpan.FromMilliseconds(150); + resizeTimer.IsEnabled = false; + resizeTimer.Tick += OnResizeEnd; + + Components = new ObservableCollection(); + + //Uri NewTheme = new Uri(@"/RealTimeGraphEx;component/Resources/Resources.xaml", UriKind.Relative); + //ResourceDictionary dictionary = (ResourceDictionary)Application.LoadComponent(NewTheme); + //Application.Current.Resources.MergedDictionaries.Add(dictionary); + + Initialize(); + } + + #endregion + + #region Virtual Methods + + /// + /// Enabled/Disabled the rendering of the graph's image. + /// + protected virtual void ChangeRenderMode(bool enabled) + { + _disableRendering = !enabled; + } + + /// + /// Raises the BeforeRenderVisuals event. + /// + /// + protected virtual void OnBeforeRenderingVisuals(WriteableBitmap bmp, double scaleFactor) + { + if (BeforeRenderVisuals != null) BeforeRenderVisuals(bmp, scaleFactor); + } + + /// + /// Raises the AfterRenderVisuals event. + /// + /// + protected virtual void OnAfterRenderingVisuals(WriteableBitmap bmp, double scaleFactor) + { + if (AfterRenderVisuals != null) AfterRenderVisuals(bmp, scaleFactor); + } + + /// + /// Override this method to apply logic when graph resize is complete. + /// + /// + /// + protected virtual void OnResizeEnd(object sender, EventArgs e) + { + resizeTimer.IsEnabled = false; + resizeTimer.Stop(); + OnSetCrossThreadFields(); + isAfterResize = true; + } + + /// + /// Override this method to apply logic while graph is resizing. + /// + /// + /// + protected virtual void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + resizeTimer.IsEnabled = true; + resizeTimer.Stop(); + resizeTimer.Start(); + } + + /// + /// Initializes the control. + /// + protected virtual void Initialize() + { + //Initialize the main grid + gridMain = new Grid() { ClipToBounds = true }; + + gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto }); + gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); + gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto }); + + //Initialize the grid lines and image wrapper grid. + gridLinesAndImageWrapperGrid = new Grid() { Background = Brushes.Transparent }; + + //Initialize the grid lines grid. + gridBack = new Grid(); + + //Initialize the graph image. + img = new Image() { Stretch = Stretch.None, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Stretch }; + + //Initialize the Y Axis grid. + stackLeft = new StackPanel() { HorizontalAlignment = HorizontalAlignment.Left, Orientation = Orientation.Horizontal, FlowDirection = System.Windows.FlowDirection.LeftToRight }; + + //Initializing the X Axis grid. + stackRight = new StackPanel() { HorizontalAlignment = HorizontalAlignment.Right, Orientation = Orientation.Horizontal, FlowDirection = System.Windows.FlowDirection.RightToLeft }; + + //Initialize the inner content wrapper. + gridInnerContentWrapper = new Grid(); + + //Add the main grid to the control. + this.Content = gridMain; + + //Add the wrapper grid to the main grid. + gridMain.Children.Add(gridLinesAndImageWrapperGrid); + + //Add the grid lines grid to the wrapper. + gridLinesAndImageWrapperGrid.Children.Add(gridBack); + + //Add the graph image to the wrapper. + gridMain.Children.Add(img); + img.IsHitTestVisible = false; + Grid.SetColumnSpan(img, 3); + + //Add the left grid to the main grid. + gridMain.Children.Add(stackLeft); + + //Add the right grid to the main grid. + gridMain.Children.Add(stackRight); + + gridMain.Children.Add(gridInnerContentWrapper); + + Grid.SetColumn(stackLeft, 0); + Grid.SetColumnSpan(stackLeft, 1); + Grid.SetColumn(stackRight, 2); + Grid.SetColumnSpan(stackRight, 1); + Grid.SetColumn(gridLinesAndImageWrapperGrid, 1); + Grid.SetColumnSpan(gridLinesAndImageWrapperGrid, 1); + Grid.SetColumn(gridInnerContentWrapper, 0); + Grid.SetColumnSpan(gridInnerContentWrapper, 3); + + //Set size changed event to get the exact size of image grid! + gridLinesAndImageWrapperGrid.SizeChanged += OnImageGridSizeChanged; + + //Zooming + //scaleTransform = new ScaleTransform(1, 1); + //translateTransform = new TranslateTransform(0, 0); + //TransformGroup group = new TransformGroup(); + //group.Children.Add(scaleTransform); + //group.Children.Add(translateTransform); + //gridLinesAndImageWrapperGrid.RenderTransform = group; + gridLinesAndImageWrapperGrid.PreviewMouseWheel += gridLinesAndImageWrapperGrid_PreviewMouseWheel; + moveThumb = new Thumb() { HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch, Opacity = 0 }; + if (!gridLinesAndImageWrapperGrid.Children.Contains(moveThumb)) + { + gridLinesAndImageWrapperGrid.Children.Add(moveThumb); + moveThumb.DragDelta += moveThumb_DragDelta; + } + + selectionCanvas = new Canvas() { Background = null, HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch }; + Panel.SetZIndex(moveThumb, 9999); + Grid.SetColumnSpan(selectionCanvas, 3); + gridMain.Children.Add(selectionCanvas); + selectionRectangle = new Rectangle() { Fill = SelectionFill, Stroke = SelectionStroke }; + + gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown; + selectionCanvas.MouseMove += selectionCanvas_MouseMove; + selectionCanvas.PreviewMouseUp += selectionCanvas_PreviewMouseUp; + gridLinesAndImageWrapperGrid.PreviewMouseLeftButtonDown += gridLinesAndImageWrapperGrid_PreviewMouseLeftButtonDown; + gridLinesAndImageWrapperGrid.MouseMove += OnMouseMove; + gridLinesAndImageWrapperGrid.MouseLeave += OnMouseLeave; + this.SizeChanged += (x, y) => { if (isScaled) { ResetZoom(); }; }; + } + + /// + /// Resets the graph zooming. + /// + public virtual void ResetZoom() + { + if (!isScaled) return; + + isScaled = false; + + gridLinesAndImageWrapperGrid.RenderTransformOrigin = new Point(0.5, 0.5); + + DoubleAnimation aniX = new DoubleAnimation(); + aniX.Duration = new Duration(TimeSpan.FromSeconds(0.1)); + aniX.To = gridMain.ActualWidth; + + DoubleAnimation aniY = new DoubleAnimation(); + aniY.Duration = new Duration(TimeSpan.FromSeconds(0.1)); + aniY.To = gridMain.ActualHeight; + + ThicknessAnimation marginAni = new ThicknessAnimation(); + marginAni.Duration = new Duration(TimeSpan.FromSeconds(0.1)); + marginAni.To = new Thickness(0, 0, 0, 0); + + if (!double.IsNaN(gridLinesAndImageWrapperGrid.Width)) + { + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, aniX); + } + + if (!double.IsNaN(gridLinesAndImageWrapperGrid.Height)) + { + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, aniY); + } + + aniY.CurrentTimeInvalidated += (x, y) => + { + ApplyVirtualMaximumMinimum(); + }; + + marginAni.Completed += (x, y) => + { + if (!double.IsNaN(gridLinesAndImageWrapperGrid.Width)) + { + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, null); + } + + if (!double.IsNaN(gridLinesAndImageWrapperGrid.Height)) + { + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, null); + } + + gridLinesAndImageWrapperGrid.Width = double.NaN; + gridLinesAndImageWrapperGrid.Height = double.NaN; + + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, null); + gridLinesAndImageWrapperGrid.Margin = new Thickness(0, 0, 0, 0); + + ApplyVirtualMaximumMinimum(); + + Thread t = new Thread(() => + { + Thread.Sleep(100); + + this.Dispatcher.BeginInvoke(new Action(() => + { + OnZoomingComplete(new Point(0.5, 0.5), gridLinesAndImageWrapperGrid.ActualWidth, gridLinesAndImageWrapperGrid.ActualHeight); + }), DispatcherPriority.Background); + }); + t.Start(); + }; + + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, marginAni); + gridLinesAndImageWrapperGrid.Cursor = Cursors.Arrow; + + gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown; + } + + /// + /// Override this method to apply extra logic when the grid containing the graph and grid lines changes its size. + /// + /// + /// + protected virtual void OnImageGridSizeChanged(object sender, SizeChangedEventArgs e) + { + OnSetCrossThreadFields(); + } + + /// + /// Occurs after the loaded graph event. + /// + /// Graph instance. + /// Arguments + protected virtual void OnGraphLoaded(object sender, RoutedEventArgs e) + { + if (!_loaded) + { + _loaded = true; + + if (InnerContent != null) + { + gridInnerContentWrapper.Children.Clear(); + gridInnerContentWrapper.Children.Add(InnerContent); + } + + OnRenderComponents(); + + OnSetCrossThreadFields(); + } + } + + /// + /// Set the RealTimeGraphCrossThreadModel properties used by the graph thread. + /// + protected virtual void OnSetCrossThreadFields() + { + this.Dispatcher.Invoke(() => + { + _antialiased = Antialiased; + _height = gridLinesAndImageWrapperGrid.ActualHeight; + _width = gridLinesAndImageWrapperGrid.ActualWidth; + _mainWidth = gridMain.ActualWidth; + _mainHeight = gridMain.ActualHeight; + _maximum = Maximum; + _minimum = Minimum; + _originalMinimum = Minimum; + _refreshRate = RefreshRate; + _isPaused = IsPaused; + virtualMinimum = Minimum; + virtualMaximum = Maximum; + virtualStart = 0; + virtualEnd = gridMain.ActualWidth; + RenderOptions.SetEdgeMode(this, _antialiased ? EdgeMode.Unspecified : EdgeMode.Aliased); + + }, DispatcherPriority.Send); + } + + /// + /// Override this method to apply extra logic for clearing the graph. + /// + protected virtual void OnClearGraph() + { + + } + + /// + /// Override this method to render the graph image. + /// + protected internal virtual void OnRenderGraph() + { + + } + + /// + /// Override this method to set the cross thread model properties. + /// + /// + protected virtual void OnGraphPropertiesChanged(DependencyPropertyChangedEventArgs e) + { + OnSetCrossThreadFields(); + } + + /// + /// Convert the specified integer to graph Y position. + /// + /// + /// + protected virtual double ConvertYToImageY(double value) + { + double valuePrecentage = ((((value - _minimum) * 100) / (_maximum - _minimum)) * _height) / 100; + return valuePrecentage; + } + + /// + /// Convert the specified integer to graph X position. + /// + /// + /// + protected virtual double ConvertYToImageX(double value) + { + double valuePrecentage = ((((value - _minimum) * 100) / (_maximum - _minimum)) * _width) / 100; + return valuePrecentage; + } + + /// + /// Convert the specified integer to graph flipped Y position. + /// + /// + /// + protected virtual double ConvertYToImageYFliped(double value) + { + double valuePrecentage = ConvertYToImageY(value); + valuePrecentage = _height - valuePrecentage; //Flip + return valuePrecentage; + } + + /// + /// Renders the collection of graph components. + /// + protected virtual void OnRenderComponents() + { + if (Components != null) + { + foreach (var component in Components) + { + component.Graph = this; + component.Render(this); + + if (component.Location == ComponentLocationEnum.Left) + { + if (!stackLeft.Children.Contains(component)) + { + stackLeft.Children.Add(component); + } + } + else if (component.Location == ComponentLocationEnum.Right) + { + if (!stackRight.Children.Contains(component)) + { + stackRight.Children.Add(component); + } + } + else if (component.Location == ComponentLocationEnum.Back) + { + if (!gridBack.Children.Contains(component)) + { + gridBack.Children.Add(component); + } + } + else if (component.Location == ComponentLocationEnum.Front) + { + if (!gridInnerContentWrapper.Children.Contains(component)) + { + gridInnerContentWrapper.Children.Add(component); + } + } + + } + } + } + + /// + /// Sets the current state of the graph. + /// + /// if set to true [paused]. + protected virtual void SetPaused(bool paused) + { + this.Dispatcher.Invoke(() => + { + + IsPaused = paused; + + }); + } + + /// + /// Called when [zooming to]. + /// + /// The transform origin. + /// The scale markerPosition. + /// The scale y. + protected virtual void OnZoomingComplete(Point transformOrigin, double scaleX, double scaleY) + { + if (ZoomComplete != null) ZoomComplete(transformOrigin, scaleX, scaleY); + } + + /// + /// Called when [panning to]. + /// + /// The translate. + protected virtual void OnPanningComplete(Point translate) + { + if (PanningComplete != null) PanningComplete(translate); + } + + /// + /// Converts the y position to value. + /// + /// The Y Position. + /// The Y Value + protected virtual double ConvertYPositionToValue(double y) + { + double valuePrecentage = (y * 100) / gridLinesAndImageWrapperGrid.ActualHeight; + valuePrecentage = (valuePrecentage * (_maximum - _minimum)) / 100; + valuePrecentage = _minimum + (_maximum - _minimum) - valuePrecentage; //Flip + return valuePrecentage; + } + + /// + /// Add an IRealTimeGraphComponent instance to the components collection. + /// + /// + internal virtual void ApplyComponent(FrameworkElement component) + { + if (!stackLeft.Children.Contains(component) && !stackLeft.Children.Contains(component)) + { + if (component.HorizontalAlignment == System.Windows.HorizontalAlignment.Right) + { + stackRight.Children.Add(component); + } + else + { + stackLeft.Children.Add(component); + } + } + } + + /// + /// Called when [mouse move]. + /// + /// The sender. + /// The instance containing the event data. + protected virtual void OnMouseMove(object sender, MouseEventArgs e) + { + MouseValue = ConvertYPositionToValue(e.GetPosition(gridLinesAndImageWrapperGrid).Y); + if (MouseMove != null) MouseMove(this, e); + } + + /// + /// Called when [mouse leave]. + /// + /// The sender. + /// The instance containing the event data. + protected virtual void OnMouseLeave(object sender, MouseEventArgs e) + { + if (MouseLeave != null) MouseLeave(this, e); + } + + /// + /// Normalizes the value. + /// + /// The value. + protected virtual void NormalizeValue(ref double value) + { + if (value > _maximum) + { + value = _maximum; + } + else if (value < _originalMinimum) + { + value = _originalMinimum; + } + } + + /// + /// Override to apply extra logic when dragging on zoom mode. + /// + /// The instance containing the event data. + protected virtual void OnDragging(DragDeltaEventArgs e) + { + + } + + #endregion + + #region Protected Methods + + /// + /// Clears the graph. + /// + protected void ClearGraph() + { + StopPushThread(); + xValueCounter = 0; + OnSetCrossThreadFields(); + + OnClearGraph(); + + if (img != null) + { + WriteableBitmap bmp = BitmapFactory.New((int)_width, (int)_height); + bmp.Clear(Colors.Transparent); + + bmp.Freeze(); + + img.Dispatcher.BeginInvoke(new Action(() => + { + img.Source = bmp; + }), System.Windows.Threading.DispatcherPriority.Send); + } + } + + /// + /// Starts the "push" thread. + /// + protected void StartPushThread() + { + if (pushThread == null && !IsSynced) + { + _requestTermination = new ManualResetEvent(false); + _terminated = new ManualResetEvent(false); + pushThread = new Thread(PushDataThreadMethod); + pushThread.IsBackground = true; + pushThread.Start(); + } + } + + /// + /// Stops the "push" thread. + /// + protected void StopPushThread() + { + if (pushThread != null) + { + _requestTermination.Set(); + _terminated.WaitOne(50); + pushThread = null; + } + } + + /// + /// Draw the specified polygon vectorsCollection on the graph. The drawing will be anti-aliased if specified in the cross model fields. + /// + [DebuggerStepThrough] + [DebuggerHidden] + protected void DrawPolyline(WriteableBitmap bmp, int[] points, Color stroke) + { + try + { + if (points.Length > 0) + { + if (_antialiased) + { + bmp.DrawPolylineAa(points, stroke); + } + else + { + bmp.DrawPolyline(points, stroke); + } + } + } + catch + { + Debug.WriteLine("[RealTimeGraphEx] [DrawPolyline] Error encountered while trying to Draw on bitmap."); + } + } + + /// + /// Forces events update. + /// + public static void DoEvents() + { + Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, + new Action(delegate { })); + } + + #endregion + + #region Private Methods + + /// + /// The push thread method. + /// + private void PushDataThreadMethod() + { + while (!_requestTermination.WaitOne(0)) + { + OnRenderGraph(); + Thread.Sleep(_refreshRate); + } + + _terminated.Set(); + } + + private Rect BoundsRelativeTo(FrameworkElement element, Visual relativeTo) + { + return + element.TransformToVisual(relativeTo) + .TransformBounds(LayoutInformation.GetLayoutSlot(element)); + } + + private void ApplyVirtualMaximumMinimum() + { + var b = new Rect(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top, gridLinesAndImageWrapperGrid.Width, gridLinesAndImageWrapperGrid.Height); + + double maxDecreasePrecentage = (Math.Abs(b.Top) * 100) / gridMain.ActualHeight; + double minIncreasePrecentage = ((b.Bottom - gridMain.ActualHeight) * 100) / gridMain.ActualHeight; + + + virtualMaximum = ConvertYPositionToValue(Math.Abs(b.Top)); + virtualMinimum = ConvertYPositionToValue(b.Height - Math.Abs(b.Height - (gridMain.ActualHeight - b.Top))); + virtualStart = Math.Abs(b.Left); + virtualEnd = virtualStart + gridMain.ActualWidth; + + _offSetX = gridLinesAndImageWrapperGrid.Margin.Left; + _offSetY = gridLinesAndImageWrapperGrid.Margin.Top; + } + + #endregion + + #region Public Methods + + /// + /// Gets the size of the graph render. + /// + /// + public Rect GetGraphRenderBounds() + { + var b = BoundsRelativeTo(gridLinesAndImageWrapperGrid, gridMain); + return b; + } + + #endregion + + #region Static Methods + + protected static void CrossModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + (d as RealTimeGraphExBase).OnGraphPropertiesChanged(e); + } + + #endregion + + #region Event Handlers + + /// + /// Handles the PreviewMouseWheel event of the gridLinesAndImageWrapperGrid control. + /// + /// The source of the event. + /// The instance containing the event data. + protected void gridLinesAndImageWrapperGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + if (ZoomMode == ZoomModeEnum.MouseWheel) + { + gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown; + Zoom += (e.Delta < 0 ? -0.1 : 0.1); + } + else if (ZoomMode == ZoomModeEnum.Selection) + { + + var center = new Point(e.GetPosition(gridLinesAndImageWrapperGrid).X, e.GetPosition(gridLinesAndImageWrapperGrid).Y); + + int zoomFactor = 20; + + double width = gridLinesAndImageWrapperGrid.ActualWidth + (e.Delta < 0 ? zoomFactor * -1 : zoomFactor); + double height = gridLinesAndImageWrapperGrid.ActualHeight + (e.Delta < 0 ? zoomFactor * -1 : zoomFactor); + double left = gridLinesAndImageWrapperGrid.Margin.Left; + double top = gridLinesAndImageWrapperGrid.Margin.Top; + + bool reset = true; + + if (ZoomDirection != ZoomDirectionEnum.Y) + { + if (width > gridMain.ActualWidth) + { + gridLinesAndImageWrapperGrid.Width = width; + reset = false; + } + } + + if (ZoomDirection != ZoomDirectionEnum.X) + { + if (height > gridMain.ActualHeight) + { + gridLinesAndImageWrapperGrid.Height = height; + reset = false; + } + } + + double factorX = zoomFactor / (gridLinesAndImageWrapperGrid.ActualWidth / center.X); + double factorY = zoomFactor / (gridLinesAndImageWrapperGrid.ActualHeight / center.Y); + + if (ZoomDirection != ZoomDirectionEnum.Y) + { + if (gridLinesAndImageWrapperGrid.Margin.Left + (e.Delta < 0 ? factorX : -factorX) < 0) + { + left = gridLinesAndImageWrapperGrid.Margin.Left + (e.Delta < 0 ? factorX : -factorX); + reset = false; + } + } + + if (ZoomDirection != ZoomDirectionEnum.X) + { + if (gridLinesAndImageWrapperGrid.Margin.Top + (e.Delta < 0 ? factorY : -factorY) < 0) + { + top = gridLinesAndImageWrapperGrid.Margin.Top + (e.Delta < 0 ? factorY : -factorY); + reset = false; + } + } + + gridLinesAndImageWrapperGrid.Margin = + new Thickness( + left, + top, + gridLinesAndImageWrapperGrid.Margin.Right, + gridLinesAndImageWrapperGrid.Margin.Bottom); + + OnZoomingComplete(lastCenter, gridLinesAndImageWrapperGrid.Width, gridLinesAndImageWrapperGrid.Height); + ApplyVirtualMaximumMinimum(); + + if (reset) + { + ResetZoom(); + gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown; + isScaled = false; + } + else + { + gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown; + gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll; + isScaled = true; + } + } + } + + /// + /// Handles the DragDelta event of the moveThumb control. + /// + /// The source of the event. + /// The instance containing the event data. + protected void moveThumb_DragDelta(object sender, DragDeltaEventArgs e) + { + if (ZoomMode == ZoomModeEnum.None) return; + + Point relativePoint = gridLinesAndImageWrapperGrid.TransformToAncestor(gridMain).Transform(new Point(0, 0)); + Rect bounds = BoundsRelativeTo(gridLinesAndImageWrapperGrid, gridMain); + + double right = (gridLinesAndImageWrapperGrid.ActualWidth + gridLinesAndImageWrapperGrid.Margin.Left); + double bottom = (gridLinesAndImageWrapperGrid.ActualHeight + gridLinesAndImageWrapperGrid.Margin.Top); + + if (gridLinesAndImageWrapperGrid.Margin.Left + e.HorizontalChange <= 0 && right + e.HorizontalChange >= gridMain.ActualWidth) + { + gridLinesAndImageWrapperGrid.Margin = new Thickness(gridLinesAndImageWrapperGrid.Margin.Left + e.HorizontalChange, gridLinesAndImageWrapperGrid.Margin.Top, 0, 0); + //translateTransform.X += e.HorizontalChange; + OnPanningComplete(new Point(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top)); + ApplyVirtualMaximumMinimum(); + } + + if (gridLinesAndImageWrapperGrid.Margin.Top + e.VerticalChange <= 0 && bottom + e.VerticalChange >= gridMain.ActualHeight) + { + gridLinesAndImageWrapperGrid.Margin = new Thickness(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top + e.VerticalChange, 0, 0); + //translateTransform.Y += e.VerticalChange; + OnPanningComplete(new Point(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top)); + ApplyVirtualMaximumMinimum(); + } + + OnDragging(e); + } + + /// + /// Handles the PreviewMouseLeftButtonDown event of the gridLinesAndImageWrapperGrid control. + /// + /// The source of the event. + /// The instance containing the event data. + protected void gridLinesAndImageWrapperGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (ZoomMode != ZoomModeEnum.Selection) return; + + if (e.ClickCount == 2) + { + ResetZoom(); + } + } + + /// + /// + /// + protected void selectionCanvas_PreviewMouseUp(object sender, MouseButtonEventArgs e) + { + if (ZoomMode != ZoomModeEnum.Selection) return; + + var bounds = new Rect(Canvas.GetLeft(selectionRectangle), Canvas.GetTop(selectionRectangle), selectionRectangle.Width, selectionRectangle.Height); + selectionCanvas.Children.Remove(selectionRectangle); + isSelectionMouseDown = false; + selectionCanvas.Background = null; + + if (selectionRectangle.Width <= 5 || selectionRectangle.Height <= 5) return; + + Point center = new Point(bounds.Left * (gridLinesAndImageWrapperGrid.ActualWidth / bounds.Width), bounds.Top * (gridLinesAndImageWrapperGrid.ActualHeight / bounds.Height)); + gridLinesAndImageWrapperGrid.RenderTransformOrigin = center; + + lastBounds = bounds; + lastCenter = center; + + double scaleX = gridLinesAndImageWrapperGrid.ActualWidth; + double scaleY = gridLinesAndImageWrapperGrid.ActualHeight; + + if (ZoomDirection != ZoomDirectionEnum.Y) + { + scaleX = gridLinesAndImageWrapperGrid.ActualWidth * (gridLinesAndImageWrapperGrid.ActualWidth / bounds.Width); + } + + if (ZoomDirection != ZoomDirectionEnum.X) + { + scaleY = gridLinesAndImageWrapperGrid.ActualHeight * (gridLinesAndImageWrapperGrid.ActualHeight / bounds.Height); + } + + isScaled = true; + + DoubleAnimation aniX = new DoubleAnimation(); + aniX.Duration = new Duration(TimeSpan.FromSeconds(0.1)); + aniX.To = scaleX; + + DoubleAnimation aniY = new DoubleAnimation(); + aniY.Duration = new Duration(TimeSpan.FromSeconds(0.1)); + aniY.To = scaleY; + + ThicknessAnimation marginAni = new ThicknessAnimation(); + marginAni.Duration = new Duration(TimeSpan.FromSeconds(0.1)); + marginAni.To = new Thickness(center.X * -1, center.Y * -1, 0, 0); + + aniY.CurrentTimeInvalidated += (x, y) => + { + ApplyVirtualMaximumMinimum(); + }; + + aniY.Completed += (x, y) => + { + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, null); + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, null); + gridLinesAndImageWrapperGrid.Width = scaleX; + gridLinesAndImageWrapperGrid.Height = scaleY; + + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, null); + + double marginLeft = 0; + double marginTop = 0; + + if (ZoomDirection != ZoomDirectionEnum.Y) + { + marginLeft = center.X * -1; + } + + if (ZoomDirection != ZoomDirectionEnum.X) + { + marginTop = center.Y * -1; + } + + gridLinesAndImageWrapperGrid.Margin = new Thickness(marginLeft, marginTop, 0, 0); + + OnZoomingComplete(center, scaleX, scaleY); + ApplyVirtualMaximumMinimum(); + }; + + gridLinesAndImageWrapperGrid.Width = gridLinesAndImageWrapperGrid.ActualWidth; + gridLinesAndImageWrapperGrid.Height = gridLinesAndImageWrapperGrid.ActualHeight; + + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, marginAni); + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, aniX); + gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, aniY); + gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll; + + gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown; + } + + /// + /// Handles the MouseMove event of the selectionCanvas control. + /// + /// The source of the event. + /// The instance containing the event data. + protected void selectionCanvas_MouseMove(object sender, MouseEventArgs e) + { + if (isSelectionMouseDown) + { + double width = e.GetPosition(selectionCanvas).X - Canvas.GetLeft(selectionRectangle); + double height = e.GetPosition(selectionCanvas).Y - Canvas.GetTop(selectionRectangle); + + if (width > 0) + { + selectionRectangle.Width = width; + } + + if (height > 0) + { + selectionRectangle.Height = height; + } + } + } + + /// + /// Handles the PreviewMouseDown event of the gridLinesAndImageWrapperGrid control. + /// + /// The source of the event. + /// The instance containing the event data. + protected void gridLinesAndImageWrapperGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e) + { + if (ZoomMode != ZoomModeEnum.Selection) return; + selectionCanvas.Children.Remove(selectionRectangle); + selectionCanvas.Background = Brushes.Transparent; + selectionRectangle.Width = 0; + selectionRectangle.Height = 0; + selectionCanvas.Children.Add(selectionRectangle); + Canvas.SetLeft(selectionRectangle, e.GetPosition(selectionCanvas).X); + Canvas.SetTop(selectionRectangle, e.GetPosition(selectionCanvas).Y); + isSelectionMouseDown = true; + DoEvents(); + } + + #endregion + + #region IDisposable Members + + /// + /// Disposes the current instance. + /// + public void Dispose() + { + if (pushThread != null) + { + pushThread.Abort(); + } + } + + #endregion + } +} -- cgit v1.3.1