/// <summary>
        /// Initializes a new instance of the <see cref="GraphController{TDataSeries, TXDataPoint, TYDataPoint}"/> class.
        /// </summary>
        public GraphController()
        {
            Renderer = new ScrollingLineRenderer <TDataSeries>();

            DataSeriesCollection = new ObservableCollection <TDataSeries>();
            Range = new GraphRange <TXDataPoint, TYDataPoint>();

            _last_render_time          = DateTime.Now;
            _to_render                 = new Dictionary <TDataSeries, PendingSeries>();
            _pending_series_collection = new GraphDataQueue <List <PendingSeries> >();
            RefreshRate                = TimeSpan.FromMilliseconds(50);

            ClearCommand = new GraphCommand(Clear);

            _render_thread = new Thread(RenderThreadMethod);
            _render_thread.IsBackground = true;
            _render_thread.Start();
        }
        /// <summary>
        /// The rendering thread method.
        /// </summary>
        private void RenderThreadMethod()
        {
            while (true)
            {
                if (!IsPaused)
                {
                    var pending_list = _pending_series_collection.BlockDequeue();

                    foreach (var pending_series in pending_list)
                    {
                        if (pending_series.IsClearSeries)
                        {
                            _pending_series_collection = new GraphDataQueue <List <PendingSeries> >();
                            _to_render.Clear();
                            break;
                        }

                        if (_to_render.ContainsKey(pending_series.Series))
                        {
                            var s = _to_render[pending_series.Series];
                            s.XX.AddRange(pending_series.XX);
                            s.YY.AddRange(pending_series.YY);
                        }
                        else
                        {
                            _to_render[pending_series.Series] = pending_series;
                        }
                    }

                    if (DateTime.Now > _last_render_time.AddMilliseconds(RefreshRate.TotalMilliseconds) && _to_render.Count > 0)
                    {
                        GraphDataPoint min_x = _range.MaximumX - _range.MaximumX;
                        GraphDataPoint max_x = _range.MaximumX;
                        GraphDataPoint min_y = _range.MinimumY;
                        GraphDataPoint max_y = _range.MaximumY;

                        min_x = _to_render.First().Value.XX.First();
                        max_x = _to_render.First().Value.XX.Last();

                        if (_range.AutoY)
                        {
                            min_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Min();
                            max_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Max();
                        }

                        if (min_y == max_y)
                        {
                            min_y = _range.MinimumY;
                            max_y = _range.MaximumY;
                        }

                        EffectiveMinimumX = min_x;
                        EffectiveMaximumX = max_x;
                        EffectiveMinimumY = min_y;
                        EffectiveMaximumY = max_y;

                        VirtualMinimumX = EffectiveMinimumX;
                        VirtualMaximumX = EffectiveMaximumX;
                        VirtualMinimumY = EffectiveMinimumY;
                        VirtualMaximumY = EffectiveMaximumY;

                        _last_render_time = DateTime.Now;

                        if (Surface != null)
                        {
                            var surface_size = Surface.GetSize();
                            var zoom_rect    = Surface.GetZoomRect();

                            Surface.BeginDraw();

                            if (zoom_rect.Width > 0 && zoom_rect.Height > 0)
                            {
                                var zoom_rect_top_percentage    = zoom_rect.Top / surface_size.Height;
                                var zoom_rect_bottom_percentage = zoom_rect.Bottom / surface_size.Height;
                                var zoom_rect_left_percentage   = zoom_rect.Left / surface_size.Width;
                                var zoom_rect_right_percentage  = zoom_rect.Right / surface_size.Width;

                                VirtualMinimumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_bottom_percentage);
                                VirtualMaximumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_top_percentage);

                                VirtualMinimumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_left_percentage);
                                VirtualMaximumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_right_percentage);

                                GraphTransform transform   = new GraphTransform();
                                var            scale_x     = (float)(surface_size.Width / zoom_rect.Width);
                                var            scale_y     = (float)(surface_size.Height / zoom_rect.Height);
                                var            translate_x = (float)-zoom_rect.Left * scale_x;
                                var            translate_y = (float)-zoom_rect.Top * scale_y;

                                transform            = new GraphTransform();
                                transform.TranslateX = translate_x;
                                transform.TranslateY = translate_y;
                                transform.ScaleX     = scale_x;
                                transform.ScaleY     = scale_y;

                                Surface.SetTransform(transform);
                            }

                            List <Tuple <TDataSeries, IEnumerable <PointF> > > to_draw = new List <Tuple <TDataSeries, IEnumerable <PointF> > >();

                            var to_render = _to_render.Select(x => x.Value).ToList();

                            foreach (var item in to_render)
                            {
                                var points = Renderer.Render(Surface, item.Series, _range, item.XX, item.YY, min_x, max_x, min_y, max_y);
                                to_draw.Add(new Tuple <TDataSeries, IEnumerable <PointF> >(item.Series, points));
                            }

                            for (int i = 0; i < to_draw.Count; i++)
                            {
                                if (to_draw[i].Item2.Count() > 2)
                                {
                                    if (to_draw[i].Item1.IsVisible)
                                    {
                                        Renderer.Draw(Surface, to_draw[i].Item1, to_draw[i].Item2, i, to_draw.Count);
                                    }
                                }
                            }

                            Surface.EndDraw();
                        }

                        OnEffectiveRangeChanged(EffectiveMinimumX, EffectiveMaximumX, EffectiveMinimumY, EffectiveMaximumY);
                        OnVirtualRangeChanged(VirtualMinimumX, VirtualMaximumX, VirtualMinimumY, VirtualMaximumY);
                    }
                }
                else
                {
                    Thread.Sleep(RefreshRate);
                }
            }
        }
        /// <summary>
        /// The rendering thread method.
        /// </summary>
        private void RenderThreadMethod()
        {
            while (true)
            {
                if (!IsPaused && !DisableRendering)
                {
                    try
                    {
                        List <List <PendingSeries> > pending_lists = new List <List <PendingSeries> >();

                        var pending_list_first = _pending_series_collection.BlockDequeue();
                        pending_lists.Add(pending_list_first);

                        while (_pending_series_collection.Count > 0)
                        {
                            var pending_list = _pending_series_collection.BlockDequeue();
                            pending_lists.Add(pending_list);
                        }

                        foreach (var pending_list in pending_lists)
                        {
                            foreach (var pending_series in pending_list)
                            {
                                if (pending_series.IsClearSeries)
                                {
                                    _pending_series_collection = new GraphDataQueue <List <PendingSeries> >();
                                    _to_render.Clear();
                                }
                                else if (!pending_series.IsUpdateSeries)
                                {
                                    if (_to_render.ContainsKey(pending_series.Series))
                                    {
                                        var s = _to_render[pending_series.Series];
                                        s.XX.AddRange(pending_series.XX);
                                        s.YY.AddRange(pending_series.YY);
                                    }
                                    else
                                    {
                                        _to_render[pending_series.Series] = pending_series;
                                    }
                                }
                            }
                        }

                        Render();
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine($"Error in RealTimeGraphX:\n{ex.ToString()}");
                    }
                }
                else if (IsPaused && !DisableRendering)
                {
                    Render();
                    Thread.Sleep(RefreshRate);
                }
                else
                {
                    Thread.Sleep(RefreshRate);
                }
            }
        }