aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs
blob: c5eaaf4af34f00a22cf55e62301637d6f6e74f86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
using RealTimeGraphEx.Controllers;
using RealTimeGraphEx.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace RealTimeGraphEx.ReachGraphs
{
    public class RealTimeGraphExReachMultiLineScroll : RealTimeGraphExMultiBase
    {
        #region Cross Thread Fields

        protected List<RealTimeGraphExReachPolygon> _graphPolygons;
        protected List<Polygon> polygons;
        protected Brush _stroke;
        protected Brush _fill;
        protected bool _fillGraph;

        #endregion

        #region Constructors

        public RealTimeGraphExReachMultiLineScroll()
            : base()
        {
            _graphPolygons = new List<RealTimeGraphExReachPolygon>();
            _graphController = new GraphMultiController();
            polygons = new List<Polygon>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the graph strokes color.
        /// </summary>
        public Brush Stroke
        {
            get { return (Brush)GetValue(StrokeProperty); }
            set { SetValue(StrokeProperty, value); }
        }
        public static readonly DependencyProperty StrokeProperty =
            DependencyProperty.Register("Stroke", typeof(Brush), typeof(RealTimeGraphExReachMultiLineScroll), new PropertyMetadata(Brushes.Black, new PropertyChangedCallback(CrossModelChanged)));

        /// <summary>
        /// Gets or sets the graph fill color.
        /// </summary>
        public Brush Fill
        {
            get { return (Brush)GetValue(FillProperty); }
            set { SetValue(FillProperty, value); }
        }
        public static readonly DependencyProperty FillProperty =
            DependencyProperty.Register("Fill", typeof(Brush), typeof(RealTimeGraphExReachMultiLineScroll), new PropertyMetadata(null, new PropertyChangedCallback(CrossModelChanged)));

        /// <summary>
        /// Gets or sets whether the graph will be rendered using the Fill color property.
        /// </summary>
        public bool FillGraph
        {
            get { return (bool)GetValue(FillGraphProperty); }
            set { SetValue(FillGraphProperty, value); }
        }
        public static readonly DependencyProperty FillGraphProperty =
            DependencyProperty.Register("FillGraph", typeof(bool), typeof(RealTimeGraphExReachMultiLineScroll), new PropertyMetadata(false, new PropertyChangedCallback(CrossModelChanged)));

        #endregion

        #region Override Methods

        protected override void OnControllerChanged()
        {
            if (_graphController != null)
            {
                _graphController.RegisterMethods(ClearGraph, StartPushThread, SetPaused, ChangeRenderMode, null);
                polygons.Clear();
                gridLinesAndImageWrapperGrid.Children.Remove(img);

                foreach (var dataSeries in _graphController.DataSeriesCollection)
                {
                    _graphPolygons.Add(new RealTimeGraphExReachPolygon());
                    Polygon polygon = new Polygon();
                    polygons.Add(polygon);
                    gridLinesAndImageWrapperGrid.Children.Add(polygon);
                }
            }
        }

        protected override void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            OnSetCrossThreadFields();
        }

        protected override void OnSetCrossThreadFields()
        {
            base.OnSetCrossThreadFields();

            this.Dispatcher.Invoke(() =>
            {
                _maxPoints = MaxPoints;
                _scaleFactor = _width / _maxPoints;
                _graphController = Controller;
                _stroke = Stroke;
                _fill = Fill;

                if (_graphController != null && _graphController.dataSeriesCollection != null && _graphController.dataSeriesCollection.Count > 0)
                {
                    for (int i = 0; i < _graphController.dataSeriesCollection.Count; i++)
                    {
                        polygons[i].Fill = _graphController.dataSeriesCollection[i].fill;
                        polygons[i].Stroke = _graphController.dataSeriesCollection[i].stroke;
                        polygons[i].Margin = new Thickness(-2, 1, -2, 0);
                    }
                }



            }, System.Windows.Threading.DispatcherPriority.Send);
        }

        protected override void OnClearGraph()
        {
            _graphPolygons.ForEach(x => x.ClearPoints());
            _graphPolygons.ForEach(x => x.ClearActualPoints());

            this.Dispatcher.Invoke(() =>
            {
                foreach (var polygon in polygons)
                {
                    polygon.Points.Clear();
                }
            });

            base.OnClearGraph();
        }

        protected internal override void OnRenderGraph()
        {
            if (_graphController != null && _graphController.dataSeriesCollection.Count > 0 && _graphController.TotalPoints > 0 && _width > 1 && _height > 1)
            {
                for (int i = 0; i < _graphController.dataSeriesCollection.Count; i++)
                {
                    double seriesCounter = xValueCounter;


                    for (int j = 0; j < _graphController.dataSeriesCollection[i].Points.Count; j++)
                    {
                        double value = _graphController.dataSeriesCollection[i].Points[j];

                        double valuePrecentage = value;

                        _graphPolygons[i].Add(seriesCounter, valuePrecentage, value);
                        seriesCounter += _scaleFactor;
                    }
                }

                for (int i = 0; i < _graphController.dataSeriesCollection[0].Points.Count; i++)
                {
                    xValueCounter += _scaleFactor;
                }

                if (_graphPolygons[0].TotalPoints > _maxPoints + _graphController.dataSeriesCollection[0].Points.Count)
                {
                    xValueCounter -= (_scaleFactor * (_graphPolygons[0].TotalPoints - (_maxPoints + _graphController.dataSeriesCollection[0].Points.Count)));

                    foreach (var polygon in _graphPolygons)
                    {
                        polygon.RemoveFromStart(polygon.TotalPoints - (_maxPoints + _graphController.dataSeriesCollection[0].Points.Count));
                    }
                }


                updateCounter++;

                if (updateCounter >= 1)
                {
                    updateCounter = 0;

              
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

    /// <summary>
    /// Represents an abstract base class for the RealTimeGraphEx.
    /// </summary>
    [ContentProperty("Components")]
    public abstract class RealTimeGraphExBase : UserControl, IDisposable
    {
        private Rect lastBounds;
        private Point lastCenter;
        private bool _loaded;

        #region Events

        /// <summary>
        /// Occurs just before rendering the actual visuals on the writeable bitmap.
        /// </summary>
        public event Action<WriteableBitmap, double> BeforeRenderVisuals;

        /// <summary>
        /// Occurs after rendering the actual visuals on the writeable bitmap.
        /// </summary>
        public event Action<WriteableBitmap, double> AfterRenderVisuals;

        /// <summary>
        /// Occurs when [zooming to].
        /// </summary>
        public event ZoomCompleteDelegate ZoomComplete;

        /// <summary>
        /// Occurs when [panning to].
        /// </summary>
        public event PanningCompleteDelegate PanningComplete;

        /// <summary>
        /// Occurs when the mouse pointer moves while over this element.
        /// </summary>
        public new event MouseEventHandler MouseMove;

        /// <summary>
        /// Occurs when the mouse pointer leaves the bounds of this element.
        /// </summary>
        public new event MouseEventHandler MouseLeave;

        /// <summary>
        /// Occurs when [minimum maximum changed].
        /// </summary>
        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

        /// <summary>
        /// Gets or sets whether this graph is controlled by a synchronization manager.
        /// </summary>
        internal bool IsSynced { get; set; }

        /// <summary>
        /// Gets or sets an extra content which will be placed on top of the graph control.
        /// </summary>
        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);
        }

        /// <summary>
        /// Gets or sets the maximum expected value to be plotted on the graph (default 255).
        /// </summary>
        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());
             }));

        /// <summary>
        /// Gets or sets the minimum expected value to be plotted on the graph (default 0).
        /// </summary>
        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());
            }));



        /// <summary>
        /// Gets or sets the graph refresh rate in milliseconds (default 30, affects performance).
        /// </summary>
        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)));

        /// <summary>
        /// Gets or sets whether the graph will be rendered antialiased (affects performance).
        /// </summary>
        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)));

        /// <summary>
        /// Gets or sets the collection of graph add-on components.
        /// </summary>
        public ObservableCollection<ComponentBase> Components
        {
            get { return (ObservableCollection<ComponentBase>)GetValue(ComponentsProperty); }
            set { SetValue(ComponentsProperty, value); }
        }
        public static readonly DependencyProperty ComponentsProperty =
            DependencyProperty.Register("Components", typeof(ObservableCollection<ComponentBase>), typeof(RealTimeGraphExBase), new PropertyMetadata(null, new PropertyChangedCallback(ComponentsChanged)));
        private static void ComponentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as RealTimeGraphExBase).OnRenderComponents();
        }

        /// <summary>
        /// Gets or sets a value indicating whether this graph is paused.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is paused; otherwise, <c>false</c>.
        /// </value>
        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)));

        ///// <summary>
        ///// Gets or sets the zoom level for the graph.
        ///// </summary>
        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);
        }

        /// <summary>
        /// Gets or sets the zoom mode.
        /// </summary>
        /// <value>
        /// The zoom mode.
        /// </value>
        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));

        /// <summary>
        /// Gets or sets the mouse value.
        /// </summary>
        /// <value>
        /// The mouse value.
        /// </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));

        /// <summary>
        /// Gets or sets the selection fill.
        /// </summary>
        /// <value>
        /// The selection fill.
        /// </value>
        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;
        }

        /// <summary>
        /// Gets or sets the selection fill.
        /// </summary>
        /// <value>
        /// The selection fill.
        /// </value>
        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));

        /// <summary>
        /// Gets or sets the maximum points to display on the graph (default 1000).
        /// </summary>
        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)));


        /// <summary>
        /// Gets or sets a value indicating whether [use automatic range].
        /// </summary>
        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

        /// <summary>
        /// Initializes a new instance of RealTimeGraphExBase
        /// </summary>
        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<ComponentBase>();

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

        /// <summary>
        /// Enabled/Disabled the rendering of the graph's image.
        /// </summary>
        protected virtual void ChangeRenderMode(bool enabled)
        {
            _disableRendering = !enabled;
        }

        /// <summary>
        /// Raises the BeforeRenderVisuals event.
        /// </summary>
        /// <param name="bmp"></param>
        protected virtual void OnBeforeRenderingVisuals(WriteableBitmap bmp, double scaleFactor)
        {
            if (BeforeRenderVisuals != null) BeforeRenderVisuals(bmp, scaleFactor);
        }

        /// <summary>
        /// Raises the AfterRenderVisuals event.
        /// </summary>
        /// <param name="bmp"></param>
        protected virtual void OnAfterRenderingVisuals(WriteableBitmap bmp, double scaleFactor)
        {
            if (AfterRenderVisuals != null) AfterRenderVisuals(bmp, scaleFactor);
        }

        /// <summary>
        /// Override this method to apply logic when graph resize is complete.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void OnResizeEnd(object sender, EventArgs e)
        {
            resizeTimer.IsEnabled = false;
            resizeTimer.Stop();
            OnSetCrossThreadFields();
            isAfterResize = true;
        }

        /// <summary>
        /// Override this method to apply logic while graph is resizing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            resizeTimer.IsEnabled = true;
            resizeTimer.Stop();
            resizeTimer.Start();
        }

        /// <summary>
        /// Initializes the control.
        /// </summary>
        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(); }; };
        }

        /// <summary>
        /// Resets the graph zooming.
        /// </summary>
        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;
        }

        /// <summary>
        /// Override this method to apply extra logic when the grid containing the graph and grid lines changes its size.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void OnImageGridSizeChanged(object sender, SizeChangedEventArgs e)
        {
            OnSetCrossThreadFields();
        }

        /// <summary>
        /// Occurs after the loaded graph event.
        /// </summary>
        /// <param name="sender">Graph instance.</param>
        /// <param name="e">Arguments</param>
        protected virtual void OnGraphLoaded(object sender, RoutedEventArgs e)
        {
            if (!_loaded)
            {
                _loaded = true;

                if (InnerContent != null)
                {
                    gridInnerContentWrapper.Children.Clear();
                    gridInnerContentWrapper.Children.Add(InnerContent);
                }

                OnRenderComponents();

                OnSetCrossThreadFields();
            }
        }

        /// <summary>
        /// Set the RealTimeGraphCrossThreadModel properties used by the graph thread.
        /// </summary>
        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);
        }

        /// <summary>
        /// Override this method to apply extra logic for clearing the graph.
        /// </summary>
        protected virtual void OnClearGraph()
        {

        }

        /// <summary>
        /// Override this method to render the graph image.
        /// </summary>
        protected internal virtual void OnRenderGraph()
        {

        }

        /// <summary>
        /// Override this method to set the cross thread model properties.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnGraphPropertiesChanged(DependencyPropertyChangedEventArgs e)
        {
            OnSetCrossThreadFields();
        }

        /// <summary>
        /// Convert the specified integer to graph Y position.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual double ConvertYToImageY(double value)
        {
            double valuePrecentage = ((((value - _minimum) * 100) / (_maximum - _minimum)) * _height) / 100;
            return valuePrecentage;
        }

        /// <summary>
        /// Convert the specified integer to graph X position.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual double ConvertYToImageX(double value)
        {
            double valuePrecentage = ((((value - _minimum) * 100) / (_maximum - _minimum)) * _width) / 100;
            return valuePrecentage;
        }

        /// <summary>
        /// Convert the specified integer to graph flipped Y position.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual double ConvertYToImageYFliped(double value)
        {
            double valuePrecentage = ConvertYToImageY(value);
            valuePrecentage = _height - valuePrecentage; //Flip
            return valuePrecentage;
        }

        /// <summary>
        /// Renders the collection of graph components.
        /// </summary>
        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);
                        }
                    }

                }
            }
        }

        /// <summary>
        /// Sets the current state of the graph.
        /// </summary>
        /// <param name="paused">if set to <c>true</c> [paused].</param>
        protected virtual void SetPaused(bool paused)
        {
            this.Dispatcher.Invoke(() =>
            {

                IsPaused = paused;

            });
        }

        /// <summary>
        /// Called when [zooming to].
        /// </summary>
        /// <param name="transformOrigin">The transform origin.</param>
        /// <param name="scaleX">The scale markerPosition.</param>
        /// <param name="scaleY">The scale y.</param>
        protected virtual void OnZoomingComplete(Point transformOrigin, double scaleX, double scaleY)
        {
            if (ZoomComplete != null) ZoomComplete(transformOrigin, scaleX, scaleY);
        }

        /// <summary>
        /// Called when [panning to].
        /// </summary>
        /// <param name="translate">The translate.</param>
        protected virtual void OnPanningComplete(Point translate)
        {
            if (PanningComplete != null) PanningComplete(translate);
        }

        /// <summary>
        /// Converts the y position to value.
        /// </summary>
        /// <param name="y">The Y Position.</param>
        /// <returns>The Y Value</returns>
        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;
        }

        /// <summary>
        /// Add an IRealTimeGraphComponent instance to the components collection.
        /// </summary>
        /// <param name="component"></param>
        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);
                }
            }
        }

        /// <summary>
        /// Called when [mouse move].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
        protected virtual void OnMouseMove(object sender, MouseEventArgs e)
        {
            MouseValue = ConvertYPositionToValue(e.GetPosition(gridLinesAndImageWrapperGrid).Y);
            if (MouseMove != null) MouseMove(this, e);
        }

        /// <summary>
        /// Called when [mouse leave].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
        protected virtual void OnMouseLeave(object sender, MouseEventArgs e)
        {
            if (MouseLeave != null) MouseLeave(this, e);
        }

        /// <summary>
        /// Normalizes the value.
        /// </summary>
        /// <param name="value">The value.</param>
        protected virtual void NormalizeValue(ref double value)
        {
            if (value > _maximum)
            {
                value = _maximum;
            }
            else if (value < _originalMinimum)
            {
                value = _originalMinimum;
            }
        }

        /// <summary>
        /// Override to apply extra logic when dragging on zoom mode.
        /// </summary>
        /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
        protected virtual void OnDragging(DragDeltaEventArgs e)
        {

        }
        #endregion

        #region Protected Methods

        /// <summary>
        /// Clears the graph.
        /// </summary>
        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);
            }
        }

        /// <summary>
        /// Starts the "push" thread.
        /// </summary>
        protected void StartPushThread()
        {
            if (pushThread == null && !IsSynced)
            {
                _requestTermination = new ManualResetEvent(false);
                _terminated = new ManualResetEvent(false);
                pushThread = new Thread(PushDataThreadMethod);
                pushThread.IsBackground = true;
                pushThread.Start();
            }
        }

        /// <summary>
        /// Stops the "push" thread.
        /// </summary>
        protected void StopPushThread()
        {
            if (pushThread != null)
            {
                _requestTermination.Set();
                _terminated.WaitOne(50);
                pushThread = null;
            }
        }

        /// <summary>
        /// Draw the specified polygon vectorsCollection on the graph. The drawing will be anti-aliased if specified in the cross model fields.
        /// </summary>
        [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.");
            }
        }

        /// <summary>
        /// Forces events update.
        /// </summary>
        public static void DoEvents()
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                                  new Action(delegate { }));
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// The push thread method.
        /// </summary>
        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

        /// <summary>
        /// Gets the size of the graph render.
        /// </summary>
        /// <returns></returns>
        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

        /// <summary>
        /// Handles the PreviewMouseWheel event of the gridLinesAndImageWrapperGrid control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MouseWheelEventArgs"/> instance containing the event data.</param>
        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;
                }
            }
        }

        /// <summary>
        /// Handles the DragDelta event of the moveThumb control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
        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);
        }

        /// <summary>
        /// Handles the PreviewMouseLeftButtonDown event of the gridLinesAndImageWrapperGrid control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
        protected void gridLinesAndImageWrapperGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (ZoomMode != ZoomModeEnum.Selection) return;

            if (e.ClickCount == 2)
            {
                ResetZoom();
            }
        }

        /// <summary>
        /// </summary>
        /// <returns></returns>
        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;
        }

        /// <summary>
        /// Handles the MouseMove event of the selectionCanvas control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        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;
                }
            }
        }

        /// <summary>
        /// Handles the PreviewMouseDown event of the gridLinesAndImageWrapperGrid control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        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

        /// <summary>
        /// Disposes the current instance.
        /// </summary>
        public void Dispose()
        {
            if (pushThread != null)
            {
                StopPushThread();
            }
        }

        #endregion
    }
}