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.Runtime.ExceptionServices;
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;
///
/// Occurs when [minimum maximum changed].
///
public event EventHandler MinMaxChanged;
#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, (d, e) =>
{
var control = d as RealTimeGraphExBase;
CrossModelChanged(d, e);
control.MinMaxChanged?.Invoke(control, new EventArgs());
}));
///
/// 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, (d, e) =>
{
var control = d as RealTimeGraphExBase;
CrossModelChanged(d, e);
control.MinMaxChanged?.Invoke(control, new EventArgs());
}));
///
/// 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));
///
/// Gets or sets the maximum points to display on the graph (default 1000).
///
public int MaxPoints
{
get { return (int)GetValue(MaxPointsProperty); }
set { SetValue(MaxPointsProperty, value); }
}
public static readonly DependencyProperty MaxPointsProperty =
DependencyProperty.Register("MaxPoints", typeof(int), typeof(RealTimeGraphExBase), new PropertyMetadata(1000, new PropertyChangedCallback(CrossModelChanged)));
///
/// Gets or sets a value indicating whether [use automatic range].
///
public bool UseAutoRange
{
get { return (bool)GetValue(UseAutoRangeProperty); }
set { SetValue(UseAutoRangeProperty, value); }
}
public static readonly DependencyProperty UseAutoRangeProperty =
DependencyProperty.Register("UseAutoRange", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(false, new PropertyChangedCallback(CrossModelChanged)));
#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;
protected bool _useAutoRange;
#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;
_useAutoRange = UseAutoRange;
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.
///
[HandleProcessCorruptedStateExceptions]
private void PushDataThreadMethod()
{
while (!_requestTermination.WaitOne(0))
{
try
{
OnRenderGraph();
}
catch { }
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;
}
public void Clear()
{
ClearGraph();
}
public void RenderComponents()
{
OnRenderComponents();
}
#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)
{
StopPushThread();
}
}
#endregion
}
}